diff --git a/README.md b/README.md
index 85a4877..6db4606 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Form supports all of the default Nette inputs and adds new ones
+ `addMultiWhisperer()` - same as whisperer, more options can be selected
+ `addDuplicator()` - container which can be duplicated many times
+ `addDependentSelect()` - select box that can change options via ajax based on change of another input(s)
-+ `addDependentMultiSelect()` - same as DependantSelect, but more options can be selected
++ `addDependentMultiSelect()` - same as DependentSelect, but more options can be selected
+ `addDate()` - date input, can limit min and max date
## Custom settings
@@ -41,6 +41,7 @@ Form supports all of the default Nette inputs and adds new ones
+ `addBox()` - all inputs added after this call will render in new card
+ `setFloatingLabel()` - inputs will be rendered with [floating labels](https://getbootstrap.com/docs/5.0/forms/floating-labels/)
+ `setRenderInline()` - label and input are rendered each in separate row
++ `setButtonClass()` - default CSS class for all form buttons (e.g. `rounded rounded-4`); can be overridden by `setClass()` on individual buttons
+ `setRenderManually()` - set manual render, template with same name as form is used (eg. file `MyForm.php` -> `myForm.latte`)
### Groups
@@ -51,28 +52,29 @@ Inputs are rendered in `card-body` div
Submitters, links and buttons in `card-footer`
### Container
-Container works as standard Nette Continer and has these new features
+Container works as standard Nette Container and has these new features
+ `setId()` - add html id to outer div of container
+ `showCard()` - show container as BS5 [card](https://getbootstrap.com/docs/5.0/components/card/)
+ `setTitle()` - show title of container (only works when container is rendered as card)
-+ `setColor()` - set color of conatiner (only works when container is rendered as card)
++ `setColor()` - set color of container (only works when container is rendered as card)
### Inputs
Some inputs provide new features
+ `setIcon()` - add icon to input or button (Buttons, Links, Text inputs)
+ `setColor()` - add color to input or button (Buttons, Links, Checkbox, Lists)
-+ `setTemplate()` - add custom latte template insted of basic render (All inputs)
++ `setTemplate()` - add custom latte template instead of basic render (All inputs)
+ `setPrepend()` - adds prepend part to [input group](https://getbootstrap.com/docs/5.0/forms/input-group/) (Text inputs, Select boxes)
+ `setAppend()` - adds append part to [input group](https://getbootstrap.com/docs/5.0/forms/input-group/) (Text inputs, Select boxes)
+ `setRenderInline()` - render label and input each in separate row, overwrites `renderInline` setting from Form (All non-button inputs)
+ `setFloatingLabel()` - input will be rendered with [floating labels](https://getbootstrap.com/docs/5.0/forms/floating-labels/) (Text inputs, Select box)
+ `setAutorenderSkip()` - skips rendering of input, eg. if input is rendered as part of another input with custom template (All inputs)
-+ `setTooltip()` - add icon with tooltip to input (Text inputs, Checkbox, Lists, Selec boxes)
-+ `setWrapClass()` - set class to outer div around label and input - overwrites basic `col-` class (Text inputs, Checkbox, Lists, Selec boxes)
-+ `setLabelWrapClass()` - set class to wrap div around label - overwrites basic `col-` class (Text inputs, Checkbox, Lists, Selec boxes)
-+ `setInputWrapClass()` - set class to wrap div around input - overwrites basic `col-` class (Text inputs, Checkbox, Lists, Selec boxes)
++ `setTooltip()` - add icon with tooltip to input (Text inputs, Checkbox, Lists, Select boxes)
++ `setQuickCopy()` - add button to copy input value to clipboard (Text inputs, TextArea)
++ `setWrapClass()` - set class to outer div around label and input - overwrites basic `col-` class (Text inputs, Checkbox, Lists, Select boxes)
++ `setLabelWrapClass()` - set class to wrap div around label - overwrites basic `col-` class (Text inputs, Checkbox, Lists, Select boxes)
++ `setInputWrapClass()` - set class to wrap div around input - overwrites basic `col-` class (Text inputs, Checkbox, Lists, Select boxes)
### Duplicator example
```
diff --git a/src/Form.php b/src/Form.php
index 46e312f..801df3c 100644
--- a/src/Form.php
+++ b/src/Form.php
@@ -5,10 +5,13 @@
namespace ModulIS\Form;
use Nette\Application\UI\Form as UIForm;
+use Nette\ComponentModel\IContainer;
use Nette\Forms\Controls\DateTimeControl;
+use Nette\Forms\Controls\HiddenField;
use Nette\Utils\DateTime;
use Nette\Utils\Html;
use Stringable;
+use function assert;
class Form extends UIForm
{
@@ -30,6 +33,8 @@ class Form extends UIForm
public bool $ajax = false;
+ private ?string $buttonClass = null;
+
public Html|string|null $title = null;
public ?string $icon = null;
@@ -49,7 +54,7 @@ class Form extends UIForm
private array $dividerArray = [];
- public function __construct(?\Nette\ComponentModel\IContainer $parent = null, $name = null)
+ public function __construct(?IContainer $parent = null, $name = null)
{
parent::__construct($parent, $name);
@@ -79,7 +84,7 @@ public function renderForm()
foreach($groupArray as $groupTitle => $group)
{
- \assert($group instanceof ControlGroup);
+ assert($group instanceof ControlGroup);
$inputs = null;
foreach($group->getInputArray() as $input)
@@ -95,7 +100,7 @@ public function renderForm()
/**
* Nette form hidden input
*/
- $inputs .= $input instanceof \Nette\Forms\Controls\HiddenField ? $input->getControl() : $input->render();
+ $inputs .= $input instanceof HiddenField ? $input->getControl() : $input->render();
if(array_key_exists($input->getName(), $this->dividerArray))
{
@@ -213,7 +218,7 @@ public function getSubmitterArray(): array
foreach($this->getGroups() as $group)
{
- \assert($group instanceof ControlGroup);
+ assert($group instanceof ControlGroup);
$submitterArray = array_merge($submitterArray, $group->getSubmitterArray());
}
@@ -515,6 +520,24 @@ public function setIcon(string $icon): self
}
+ /**
+ * Default class(es) for all form buttons (e.g. "rounded rounded-4").
+ * Priority: setClass() on the button overrides this.
+ */
+ public function setButtonClass(string $class): self
+ {
+ $this->buttonClass = $class;
+
+ return $this;
+ }
+
+
+ public function getButtonClass(): ?string
+ {
+ return $this->buttonClass;
+ }
+
+
public function setNoValidate(bool $noValidate = true): self
{
$this->noValidate = $noValidate;
diff --git a/src/control/Button.php b/src/control/Button.php
index 23225bb..0037f84 100644
--- a/src/control/Button.php
+++ b/src/control/Button.php
@@ -4,6 +4,7 @@
namespace ModulIS\Form\Control;
+use Kravcik\LatteFontAwesomeIcon\Extension;
use ModulIS\Form\Helper;
use Nette\Utils\Html;
@@ -13,6 +14,7 @@ class Button extends \Nette\Forms\Controls\Button implements Renderable
use Helper\Color;
use Helper\AutoRenderSkip;
use Helper\ControlClass;
+ use Helper\ButtonRounded;
public function getCoreControl(): Html|string
{
@@ -24,9 +26,15 @@ public function getCoreControl(): Html|string
$button = Html::el('button')
->name($this->getName())
- ->class('btn btn-' . $color . ($input->getAttribute('class') ? ' ' . $input->getAttribute('class') : ''))
- ->addHtml($this->icon ? \Kravcik\LatteFontAwesomeIcon\Extension::render($this->icon) : '')
- ->addHtml($label);
+ ->type('button');
+
+ $button->appendAttribute('class', 'btn px-4')
+ ->appendAttribute('class', 'btn-' . $color)
+ ->appendAttribute('class', $this->getFormButtonClass())
+ ->appendAttribute('class', (string) $input->getAttribute('class'));
+
+ $button->addHtml($this->icon ? Extension::render($this->icon) : '')
+ ->addHtml($this->translate($label));
if($this->getOption('id'))
{
diff --git a/src/control/DependentMultiSelect.php b/src/control/DependentMultiSelect.php
index 7008d19..d7f562b 100644
--- a/src/control/DependentMultiSelect.php
+++ b/src/control/DependentMultiSelect.php
@@ -39,9 +39,25 @@ public function __construct($label = null, array $parents = [], ?callable $depen
}
+ public function loadHttpData(): void
+ {
+ parent::loadHttpData();
+
+ $parentsValues = [];
+
+ foreach($this->parents as $parent)
+ {
+ $parentsValues[$parent->getName()] = $parent->getValue();
+ }
+
+ $data = $this->getDependentData([$parentsValues]);
+ $this->setItems($data->getItems());
+ }
+
+
public function getValue(): array
{
- return $this->getValue();
+ return parent::getValue();
}
diff --git a/src/control/Duplicator.php b/src/control/Duplicator.php
index 1d14bbb..66851a9 100644
--- a/src/control/Duplicator.php
+++ b/src/control/Duplicator.php
@@ -4,15 +4,26 @@
namespace ModulIS\Form\Control;
+use Closure;
+use Latte\Engine;
+use ModulIS\Form\Container;
use ModulIS\Form\DuplicatorContainer;
+use ModulIS\Form\Helper\AutoRenderSkip;
+use ModulIS\Form\Helper\Template;
use Nette;
use Nette\Application\UI\Presenter;
+use Nette\Forms\Control;
+use Nette\Forms\Form;
+use Nette\Forms\SubmitterControl;
use Nette\Utils\Html;
+use Nette\Utils\Strings;
+use Traversable;
+use function assert;
-class Duplicator extends \ModulIS\Form\Container implements Renderable
+class Duplicator extends Container implements Renderable
{
- use \ModulIS\Form\Helper\AutoRenderSkip;
- use \ModulIS\Form\Helper\Template;
+ use AutoRenderSkip;
+ use Template;
public bool $forceDefault = false;
@@ -48,7 +59,7 @@ public function __construct($factory, int $createDefault = 0, bool $forceDefault
$this->loadHttpData();
$this->createDefault();
});
- $this->monitor(\Nette\Forms\Form::class);
+ $this->monitor(Form::class);
if(!self::$containerClass)
{
@@ -57,7 +68,7 @@ public function __construct($factory, int $createDefault = 0, bool $forceDefault
try
{
- $this->factoryCallback = \Closure::fromCallable($factory);
+ $this->factoryCallback = Closure::fromCallable($factory);
}
catch(Nette\InvalidArgumentException $e)
{
@@ -96,7 +107,7 @@ public function render(): Html|string
{
if($this->templatePath)
{
- return (new \Latte\Engine)->renderToString($this->templatePath, $this);
+ return (new Engine)->renderToString($this->templatePath, $this);
}
if($this->autoRenderSkip === true)
@@ -124,7 +135,7 @@ public function render(): Html|string
foreach($this->getComponents() as $key => $container)
{
- \assert($container instanceof DuplicatorContainer || $container instanceof DuplicatorCreateSubmit);
+ assert($container instanceof DuplicatorContainer || $container instanceof DuplicatorCreateSubmit);
if($container instanceof DuplicatorCreateSubmit)
{
continue;
@@ -142,7 +153,7 @@ public function render(): Html|string
foreach($container->getComponents() as $duplicatorInput)
{
- \assert($duplicatorInput instanceof Renderable);
+ assert($duplicatorInput instanceof Renderable);
if($duplicatorInput instanceof Button || $duplicatorInput instanceof DuplicatorRemoveSubmit || $duplicatorInput instanceof Link)
{
$buttons .= $duplicatorInput->render();
@@ -191,7 +202,7 @@ public function render(): Html|string
}
$card = Html::el('div')
- ->id('container' . \Nette\Utils\Strings::capitalize($this->getName()))
+ ->id('container' . Strings::capitalize($this->getName()))
->class($duplicatorContainerClass)
->addHtml($header . $body . $footer);
@@ -203,19 +214,19 @@ public function render(): Html|string
public function setFactory($factory): void
{
- $this->factoryCallback = \Closure::fromCallable($factory);
+ $this->factoryCallback = Closure::fromCallable($factory);
}
public function getContainers(?bool $recursive = false)
{
- return $this->getComponents($recursive, \ModulIS\Form\Container::class);
+ return $this->getComponents($recursive, Container::class);
}
public function getButtons(?bool $recursive = false)
{
- return $this->getComponents($recursive, Nette\Forms\SubmitterControl::class);
+ return $this->getComponents($recursive, SubmitterControl::class);
}
@@ -234,8 +245,7 @@ protected function createComponent($name): ?Nette\ComponentModel\IComponent
private function getFirstControlName()
{
- $components = $this->getComponents(false, \Nette\Forms\Control::class);
- $controls = is_array($components) ? $components : iterator_to_array($components);
+ $controls = $this->getComponents(false, Control::class);
$firstControl = reset($controls);
/* @phpstan-ignore-next-line */
return $firstControl ? $firstControl->name : null;
@@ -299,7 +309,7 @@ public function setValues(array|object $values, bool $erase = false, bool $onlyD
{
foreach($values as $name => $value)
{
- if((is_array($value) || $value instanceof \Traversable) && !$this->getComponent(strval($name), false))
+ if((is_array($value) || $value instanceof Traversable) && !$this->getComponent(strval($name), false))
{
$this->createOne($name);
}
@@ -319,7 +329,7 @@ protected function loadHttpData()
foreach((array) $this->getHttpData() as $name => $value)
{
- if((is_array($value) || $value instanceof \Traversable) && !$this->getComponent(strval($name), false))
+ if((is_array($value) || $value instanceof Traversable) && !$this->getComponent(strval($name), false))
{
$this->createOne($name);
}
@@ -355,7 +365,7 @@ private function getHttpData()
{
if($this->httpPost === null)
{
- $path = explode(self::NameSeparator, $this->lookupPath(\Nette\Forms\Form::class));
+ $path = explode(self::NameSeparator, $this->lookupPath(Form::class));
$this->httpPost = Nette\Utils\Arrays::get($this->getForm()->getHttpData(), $path, null);
}
@@ -401,14 +411,14 @@ public function isAllFilled(array $exceptChildren = []): bool
{
$components = [];
- foreach($this->getComponents(false, \Nette\Forms\Control::class) as $control)
+ foreach($this->getComponents(false, Control::class) as $control)
{
$components[] = $control->getName();
}
foreach($this->getContainers() as $container)
{
- foreach($container->getComponents(true, \Nette\Forms\SubmitterControl::class) as $button)
+ foreach($container->getComponents(true, SubmitterControl::class) as $button)
{
$exceptChildren[] = $button->getName();
}
diff --git a/src/control/DuplicatorCreateSubmit.php b/src/control/DuplicatorCreateSubmit.php
index 7ebdf70..16ee47f 100644
--- a/src/control/DuplicatorCreateSubmit.php
+++ b/src/control/DuplicatorCreateSubmit.php
@@ -4,19 +4,23 @@
namespace ModulIS\Form\Control;
+use Kravcik\LatteFontAwesomeIcon\Extension;
+use ModulIS\Form\Form;
+use ModulIS\Form\FormComponent;
use Nette\Utils\Html;
+use function assert;
class DuplicatorCreateSubmit extends SubmitButton
{
- public function addCreateOnClick(bool $allowEmpty = true, ?callable $callback = null)
+ public function addCreateOnClick(bool $allowEmpty = true, ?callable $callback = null): void
{
$this->onClick[] = function(\Nette\Forms\Controls\SubmitButton $button) use ($allowEmpty, $callback): void
{
$form = $button->getForm();
- \assert($form instanceof \ModulIS\Form\Form);
+ assert($form instanceof Form);
$duplicator = $button->lookup(Duplicator::class);
- \assert($duplicator instanceof Duplicator);
+ assert($duplicator instanceof Duplicator);
if($allowEmpty === true || $duplicator->isAllFilled() === true)
{
@@ -24,8 +28,8 @@ public function addCreateOnClick(bool $allowEmpty = true, ?callable $callback =
if($form->getPresenter()->isAjax())
{
- $component = $button->lookup(\ModulIS\Form\FormComponent::class);
- \assert($component instanceof \ModulIS\Form\FormComponent);
+ $component = $button->lookup(FormComponent::class);
+ assert($component instanceof FormComponent);
$component->redrawControl('form');
}
@@ -54,13 +58,18 @@ public function getCoreControl(): Html
$currentClass = $this->getControl()->getAttribute('class');
- $icon = \Kravcik\LatteFontAwesomeIcon\Extension::render($this->isDisabled() ? 'info' : 'plus');
+ $icon = Extension::render($this->isDisabled() ? 'info' : 'plus');
$form = $this->getForm();
- \assert($form instanceof \ModulIS\Form\Form);
+ assert($form instanceof Form);
+
+ $class = 'btn' . $this->getFormButtonClass()
+ . ' btn-outline-primary btn-sm float-start'
+ . ($form->ajax ? ' ajax' : '')
+ . ($currentClass ? ' ' . $currentClass : '');
return Html::el('button')
- ->class('btn btn-outline-primary float-left btn-xs ' . ($form->ajax ? 'ajax' : '') . ($currentClass ? ' ' . $currentClass : ''))
+ ->class($class)
->addAttributes($attributes)
->disabled($this->isDisabled())
->addHtml($icon . $this->translate($this->getCaption()));
diff --git a/src/control/DuplicatorRemoveSubmit.php b/src/control/DuplicatorRemoveSubmit.php
index cfa65a0..0f13111 100644
--- a/src/control/DuplicatorRemoveSubmit.php
+++ b/src/control/DuplicatorRemoveSubmit.php
@@ -4,16 +4,20 @@
namespace ModulIS\Form\Control;
+use Kravcik\LatteFontAwesomeIcon\Extension;
+use ModulIS\Form\Form;
+use ModulIS\Form\FormComponent;
use Nette\Utils\Html;
+use function assert;
class DuplicatorRemoveSubmit extends SubmitButton
{
- public function addRemoveOnClick(?callable $callback = null)
+ public function addRemoveOnClick(?callable $callback = null): void
{
$this->onClick[] = function(\Nette\Forms\Controls\SubmitButton $button) use ($callback): void
{
$duplicator = $button->lookup(Duplicator::class);
- \assert($duplicator instanceof Duplicator);
+ assert($duplicator instanceof Duplicator);
if(is_callable($callback))
{
@@ -21,12 +25,12 @@ public function addRemoveOnClick(?callable $callback = null)
}
$form = $button->getForm(false);
- \assert($form instanceof \ModulIS\Form\Form);
+ assert($form instanceof Form);
if($form->getPresenter()->isAjax())
{
- $component = $button->lookup(\ModulIS\Form\FormComponent::class);
- \assert($component instanceof \ModulIS\Form\FormComponent);
+ $component = $button->lookup(FormComponent::class);
+ assert($component instanceof FormComponent);
$component->redrawControl('form');
}
@@ -47,16 +51,18 @@ public function getCoreControl(): Html
];
$form = $this->getForm();
- \assert($form instanceof \ModulIS\Form\Form);
+ assert($form instanceof Form);
$currentClass = $this->getControl()->getAttribute('class');
+ $class = 'btn' . $this->getFormButtonClass()
+ . ' btn-sm btn-outline-danger float-end'
+ . ($form->ajax ? ' ajax' : '')
+ . ($currentClass ? ' ' . $currentClass : '');
- $button = Html::el('button')
- ->class('btn btn-xs btn-outline-danger float-end' . ($form->ajax ? ' ajax' : '') . ($currentClass ? ' ' . $currentClass : ''))
+ return Html::el('button')
+ ->class($class)
->addAttributes($attributes)
->disabled($this->isDisabled())
- ->addHtml(\Kravcik\LatteFontAwesomeIcon\Extension::render('times') . $this->translate($this->getCaption()));
-
- return $button;
+ ->addHtml(Extension::render('times') . $this->translate($this->getCaption()));
}
}
diff --git a/src/control/Link.php b/src/control/Link.php
index 673e9bd..4e4ef53 100644
--- a/src/control/Link.php
+++ b/src/control/Link.php
@@ -13,6 +13,7 @@ class Link extends \Nette\Forms\Controls\BaseControl implements Renderable
use Helper\Color;
use Helper\AutoRenderSkip;
use Helper\ControlClass;
+ use Helper\ButtonRounded;
protected string|null $link = null;
@@ -41,7 +42,7 @@ public function getControl(): Html
}
$el->setHtml(trim($btnIcon . ' ' . $this->caption));
- $el->class('btn' . $btnColor . $currentClass);
+ $el->class('btn' . $this->getFormButtonClass() . $btnColor . $currentClass);
foreach($control->attrs as $name => $value)
{
diff --git a/src/control/SubmitButton.php b/src/control/SubmitButton.php
index 90eb0cd..0b2c3a4 100644
--- a/src/control/SubmitButton.php
+++ b/src/control/SubmitButton.php
@@ -13,6 +13,7 @@ class SubmitButton extends \Nette\Forms\Controls\SubmitButton implements Rendera
use Helper\Color;
use Helper\AutoRenderSkip;
use Helper\ControlClass;
+ use Helper\ButtonRounded;
public function getCoreControl(): Html
{
@@ -20,13 +21,23 @@ public function getCoreControl(): Html
$color = !empty($this->color) ? $this->color : 'gray';
- $button = Html::el('button')
- ->name($this->getName())
- ->class('btn rounded-pill px-4 ' . $input->getAttribute('class') . ' btn-' . $color)
+ $button = Html::el('button');
+
+ $button->name($this->getName())
+ ->appendAttribute('class', 'btn px-4')
+ ->appendAttribute('class', 'btn-' . $color)
+ ->appendAttribute('class', $this->getFormButtonClass())
+ ->appendAttribute('class', (string) $input->getAttribute('class'))
->type('submit')
- ->formnovalidate(true)
- ->addHtml($this->icon ? \Kravcik\LatteFontAwesomeIcon\Extension::render($this->icon) . ' ' : '')
- ->addHtml($this->translate($this->getCaption()));
+ ->formnovalidate(true);
+
+ if($this->icon)
+ {
+ $iconHtml = \Kravcik\LatteFontAwesomeIcon\Extension::render($this->icon);
+ $button->addHtml($iconHtml . ' ');
+ }
+
+ $button->addHtml($this->translate($this->getCaption()));
$scopeString = 'data-nette-validation-scope';
diff --git a/src/control/TextArea.php b/src/control/TextArea.php
index 64c1d85..9e33863 100644
--- a/src/control/TextArea.php
+++ b/src/control/TextArea.php
@@ -6,9 +6,10 @@
use ModulIS\Form\Helper;
-class TextArea extends \Nette\Forms\Controls\TextArea implements Renderable, FloatingRenderable, Signalable, \Nette\Application\UI\SignalReceiver
+class TextArea extends \Nette\Forms\Controls\TextArea implements Renderable, FloatingRenderable, Signalable, Helper\QuickCopyable, \Nette\Application\UI\SignalReceiver
{
use Helper\InputGroup;
+ use Helper\QuickCopy;
use Helper\Color;
use Helper\Tooltip;
use Helper\ControlPart;
diff --git a/src/control/TextInput.php b/src/control/TextInput.php
index d1b5c9f..380298c 100644
--- a/src/control/TextInput.php
+++ b/src/control/TextInput.php
@@ -6,9 +6,10 @@
use ModulIS\Form\Helper;
-class TextInput extends \Nette\Forms\Controls\TextInput implements Renderable, FloatingRenderable, Signalable, \Nette\Application\UI\SignalReceiver
+class TextInput extends \Nette\Forms\Controls\TextInput implements Renderable, FloatingRenderable, Signalable, Helper\QuickCopyable, \Nette\Application\UI\SignalReceiver
{
use Helper\InputGroup;
+ use Helper\QuickCopy;
use Helper\Color;
use Helper\Tooltip;
use Helper\ControlPart;
diff --git a/src/css/form.css b/src/css/form.css
index c3f546a..edbee7d 100644
--- a/src/css/form.css
+++ b/src/css/form.css
@@ -7,6 +7,14 @@ label{
cursor: pointer;
}
+/* In non-floating layout keep label at top for tall controls (textarea/summernote). */
+.row > .align-self-center:has(> .col-form-label),
+.row > .align-self-center:has(> .form-label)
+{
+ align-self: flex-start !important;
+ padding-top: 0.4rem;
+}
+
/*Check box*/
input[type="checkbox"] + .label-text:before,
/* summernote checkboxes */
@@ -376,6 +384,128 @@ input[type="checkbox"]:checked.checkbox-navy-dark, input[type="radio"]:checked.c
color: #212529!important;
}
+/* Input-group append: same border as form-control so it aligns visually */
+.input-group .input-group-text:not(.quick-copy-wrap)
+{
+ border: var(--bs-border-width, 1px) solid var(--bs-border-color, #dee2e6);
+}
+
+/* Quick copy button: match input height, no border, no extra space */
+.input-group:has(.quick-copy-wrap)
+{
+ overflow: visible;
+}
+.input-group .input-group-text.quick-copy-wrap
+{
+ padding: 0;
+ border: 0;
+ display: flex;
+ align-items: center;
+ flex: 0 0 auto;
+ position: relative;
+ overflow: visible;
+}
+.input-group .input-group-text.quick-copy-wrap .quick-copy-btn
+{
+ min-height: 0;
+ height: 100%;
+ width: 2.5rem;
+ min-width: 2.5rem;
+ flex: 0 0 auto;
+ overflow: hidden;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0;
+ border: 0;
+ background: #e7f1ff;
+ border-radius: 0 var(--bs-border-radius, 0.375rem) var(--bs-border-radius, 0.375rem) 0;
+ color: #0d6efd;
+}
+.input-group .input-group-text.quick-copy-wrap .quick-copy-btn:hover
+{
+ background: #cfe2ff;
+ color: #0a58ca;
+}
+.input-group .input-group-text.quick-copy-wrap .quick-copy-btn .fa,
+.input-group .input-group-text.quick-copy-wrap .quick-copy-btn i
+{
+ font-size: 1rem;
+ flex-shrink: 0;
+ color: inherit;
+}
+
+/* Quick copy popup – floating in the gap left of the button */
+.quick-copy-popup
+{
+ position: absolute;
+ right: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ margin-right: 0.35rem;
+ z-index: 1060;
+ padding: 0.35rem 0.6rem;
+ font-size: 0.8125rem;
+ color: #fff;
+ background: var(--bs-success, #198754);
+ border-radius: var(--bs-border-radius, 0.375rem);
+ box-shadow: 0 0.2rem 0.4rem rgba(0, 0, 0, 0.12);
+ white-space: nowrap;
+ pointer-events: none;
+ animation: quickCopyFadeIn 0.2s ease-out;
+}
+.quick-copy-popup.quick-copy-popup-out
+{
+ animation: quickCopyFadeOut 0.3s ease-in forwards;
+}
+@keyframes quickCopyFadeIn
+{
+ from { opacity: 0; transform: translateY(-50%) scale(0.95); }
+ to { opacity: 1; transform: translateY(-50%) scale(1); }
+}
+@keyframes quickCopyFadeOut
+{
+ from { opacity: 1; transform: translateY(-50%) scale(1); }
+ to { opacity: 0; transform: translateY(-50%) scale(0.95); }
+}
+
+/* Align whisperer (chosen) visuals with Bootstrap 5 select in non-floating mode */
+.input-group > .chosen-container
+{
+ width: 100% !important;
+}
+
+.input-group > .chosen-container .chosen-single
+{
+ display: flex;
+ align-items: center;
+ height: calc(1.5em + 0.75rem + 2px);
+ padding: 0.375rem 2.25rem 0.375rem 0.75rem;
+ border: var(--bs-border-width) solid var(--bs-border-color);
+ border-radius: var(--bs-border-radius);
+ background-color: var(--bs-form-control-bg, #fff) !important;
+ background-image: none !important;
+ box-shadow: none;
+}
+
+.input-group > .chosen-container .chosen-single.chosen-default
+{
+ background-color: var(--bs-form-control-bg, #fff) !important;
+ background-image: none !important;
+}
+
+.input-group > .chosen-container .chosen-single span
+{
+ line-height: 1.5;
+ margin-right: 0;
+ color: var(--bs-body-color);
+}
+
+.input-group > .chosen-container .chosen-single.chosen-default span
+{
+ color: #6c757d;
+}
+
.form-floating .chosen-single
{
min-width: 100%;
@@ -399,13 +529,345 @@ input[type="checkbox"]:checked.checkbox-navy-dark, input[type="radio"]:checked.c
padding-top: 0.5rem;
}
-label.required::after
+/* Keep floating input and prepend/tooltip in one row inside input-group */
+.input-group > .form-floating
+{
+ flex: 1 1 auto;
+ width: 1%;
+ min-width: 0;
+}
+
+.form-floating > label [data-bs-toggle="tooltip"]
+{
+ pointer-events: auto;
+ margin-left: 0.03rem;
+ display: inline-flex;
+ align-items: center;
+ vertical-align: middle;
+}
+
+.col-form-label.required::after,
+.form-label.required::after
{
color: #cc1d24;
content: " ★";
}
+.btn-group-active .card-body > h6 > label.required::after
+{
+ color: #cc1d24;
+ content: " ★";
+}
+
+.col-form-label.required.has-inline-required-star::after,
+.form-label.required.has-inline-required-star::after
+{
+ content: "";
+}
+
+.col-form-label .required-inline-star,
+.form-label .required-inline-star
+{
+ color: #cc1d24;
+ margin-left: 0.06rem;
+ margin-right: 0.02rem;
+}
+
+/* Hide inline required star when floating field is focused or has value */
+.form-floating > .form-control:focus ~ label .required-inline-star,
+.form-floating > .form-control:not(:placeholder-shown) ~ label .required-inline-star,
+.form-floating > .form-select ~ label .required-inline-star
+{
+ display: none;
+}
+
.form-check-inline
{
margin-right: 0 !important;
}
+
+/* Compact spacing for checkbox/radio lists rendered in row columns */
+.container > .row > .form-check
+{
+ margin-bottom: 0.15rem;
+ padding-top: 0;
+ padding-bottom: 0;
+ min-height: 0;
+}
+
+.container > .row > .form-check .form-check-label
+{
+ line-height: 1.25;
+}
+
+.container > .row
+{
+ row-gap: 0.1rem;
+}
+
+.container > .row > .form-check
+{
+ margin-bottom: 0;
+ min-height: 0;
+}
+
+/* Strong override for checkbox/radio list layout rendered by CoreList */
+.mb-3.col-12 > .row > .align-self-center.col-sm-12 > .container > .row > .form-check
+{
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ min-height: 0 !important;
+ line-height: 1.2 !important;
+}
+
+.mb-3.col-12 > .row > .align-self-center.col-sm-12 > .container > .row
+{
+ --bs-gutter-x: 0.5rem !important;
+ row-gap: 0 !important;
+}
+
+/* Filter row: variables – input height, select height */
+.datagrid table thead,
+table thead
+{
+ --dg-filter-height: 2.5em;
+ /* Only text/date inputs use this; increase to match select visual height (selects keep natural size) */
+ --dg-filter-input-height: 3em;
+}
+
+/* Text and date inputs only: raised height (selects unchanged) */
+.datagrid table thead .form-control,
+.datagrid table thead .form-control.form-control-sm,
+.datagrid table thead th .form-control,
+table thead .form-control,
+table thead th .form-control,
+table thead .form-control.form-control-sm
+{
+ height: var(--dg-filter-input-height) !important;
+ min-height: var(--dg-filter-input-height) !important;
+ padding-top: 0.28rem !important;
+ padding-bottom: 0.28rem !important;
+ box-sizing: border-box !important;
+}
+
+/* Native select (when Tom Select not used) – uses baseline height */
+.datagrid table thead .form-select:not(.ts-hidden-accessible)
+{
+ height: var(--dg-filter-height);
+ min-height: var(--dg-filter-height);
+ padding-top: 0.28rem;
+ padding-bottom: 0.28rem;
+}
+
+/* Native multiselect when Tom Select didn't run: allow one full line so text isn't cut off */
+.datagrid table thead select.form-select[multiple]:not(.ts-hidden-accessible)
+{
+ min-height: 2.5em;
+}
+
+/* Tom Select: hide original select so only .ts-control is visible */
+.datagrid table thead select.form-select.ts-hidden-accessible,
+.datagrid table thead .ts-wrapper .ts-hidden-accessible
+{
+ display: none !important;
+ position: absolute !important;
+ width: 0 !important;
+ height: 0 !important;
+ min-height: 0 !important;
+ opacity: 0 !important;
+ pointer-events: none !important;
+ clip: rect(0,0,0,0) !important;
+}
+
+/* Datagrid filter: use only Tom Select, hide Chosen if both were applied */
+.datagrid table thead .chosen-container
+{
+ display: none !important;
+}
+
+.datagrid table thead .ts-wrapper.form-select-sm
+{
+ min-height: var(--dg-filter-height) !important;
+ height: auto !important;
+}
+.datagrid table thead .ts-wrapper.form-select-sm .ts-control
+{
+ min-height: var(--dg-filter-height) !important;
+ height: auto !important;
+ padding-top: 0.28rem !important;
+ padding-bottom: 0.28rem !important;
+ box-sizing: border-box !important;
+}
+
+/* Multi only: no Bootstrap dropdown arrow, wrap tags */
+.datagrid table thead .ts-wrapper.form-select-sm.multi
+{
+ background-image: none !important;
+ --bs-form-select-bg-img: none !important;
+}
+.datagrid table thead .ts-wrapper.form-select-sm.multi .ts-control
+{
+ flex-wrap: wrap !important;
+ background-image: none !important;
+}
+.datagrid table thead .ts-wrapper.form-select-sm.multi .ts-control::after
+{
+ display: none !important;
+}
+
+/* Single select: no remove (×) button – keep only dropdown arrow */
+.datagrid table thead .ts-wrapper.form-select-sm:not(.multi) .ts-control .remove
+{
+ display: none !important;
+}
+
+.datagrid table thead .ts-wrapper.form-select-sm .ts-control > input
+{
+ line-height: 1.5 !important;
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ min-height: 0 !important;
+ height: 1.5em !important;
+ margin: 0 !important;
+}
+
+/* Multi only: when items are selected, hide placeholder "Vyberte" below the tags */
+.datagrid table thead .ts-wrapper.form-select-sm.multi.has-items .ts-control > input::placeholder
+{
+ color: transparent !important;
+ opacity: 0 !important;
+}
+
+/* Multi only: .item (tags) – no pseudo-element ×, subtle active state, remove button on top */
+.datagrid table thead .ts-wrapper.form-select-sm.multi .ts-control .item::before,
+.datagrid table thead .ts-wrapper.form-select-sm.multi .ts-control .item::after
+{
+ content: none !important;
+ display: none !important;
+}
+.datagrid table thead .ts-wrapper.form-select-sm.multi .ts-control .item.active
+{
+ background: rgba(0, 0, 0, 0.06) none !important;
+ border-color: rgba(0, 0, 0, 0.1) !important;
+ box-shadow: none !important;
+}
+.datagrid table thead .ts-wrapper.form-select-sm.multi .ts-control .item .remove
+{
+ position: relative !important;
+ z-index: 1 !important;
+}
+
+/* Datagrid date filter: open by click into input, hide calendar addon */
+.datagrid table thead .input-group.input-group-sm .form-control,
+.datagrid table thead th .input-group .form-control,
+table thead .input-group.input-group-sm .form-control,
+table thead th .input-group .form-control
+{
+ height: var(--dg-filter-input-height) !important;
+ min-height: var(--dg-filter-input-height) !important;
+}
+
+.datagrid table thead .input-group.input-group-sm
+{
+ flex-wrap: nowrap;
+}
+
+/* Same rounding on all corners so left and right match (Bootstrap often uses -sm on right only) */
+.datagrid table thead .input-group.input-group-sm > .form-control.datepicker-input,
+.datagrid table thead .input-group.input-group-sm > .form-control,
+.datagrid table thead .input-group .form-control
+{
+ flex: 1 1 auto;
+ min-width: 0;
+ border-radius: var(--bs-border-radius) !important;
+}
+
+.datagrid table thead .input-group.input-group-sm > .input-group-text
+{
+ display: none !important;
+}
+
+/* Vanilla datepicker in datagrid: keep full calendar visible and above table */
+.datepicker-dropdown
+{
+ z-index: 1080;
+}
+
+.datepicker-dropdown .datepicker-picker
+{
+ min-width: 17.5rem;
+ width: max-content;
+ border: 1px solid var(--bs-border-color);
+ border-radius: var(--bs-border-radius);
+ background-color: var(--bs-body-bg, #fff);
+}
+
+.datepicker-dropdown .datepicker-header .datepicker-controls .btn,
+.datepicker-dropdown .datepicker-picker .datepicker-header .datepicker-controls .btn
+{
+ background: transparent none !important;
+ border: 0 !important;
+ border-radius: 0 !important;
+ box-shadow: none !important;
+ min-width: auto !important;
+ padding: 0.2rem 0.45rem !important;
+}
+
+.datepicker-dropdown .datepicker-header .datepicker-controls .btn:hover,
+.datepicker-dropdown .datepicker-header .datepicker-controls .btn:focus,
+.datepicker-dropdown .datepicker-picker .datepicker-header .datepicker-controls .btn:hover,
+.datepicker-dropdown .datepicker-picker .datepicker-header .datepicker-controls .btn:focus
+{
+ background: rgba(0, 0, 0, 0.04) !important;
+ border-radius: var(--bs-border-radius-sm) !important;
+}
+
+/* Summernote rendered inside bootstrap floating wrapper */
+.form-floating > textarea.form-control[style*="display: none"] + .note-editor
+{
+ margin-top: 1.25rem;
+}
+
+.form-floating > textarea.form-control[style*="display: none"] + .note-editor + label.col-form-label,
+.form-floating > textarea.form-control[style*="display: none"] + .note-editor + label.form-label
+{
+ position: absolute !important;
+ top: 0 !important;
+ height: auto !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ transform: translateY(-30%) !important;
+ transform-origin: left center !important;
+ pointer-events: none;
+ z-index: 3;
+ overflow: visible !important;
+ white-space: nowrap !important;
+ background: none !important;
+ border: 0 !important;
+ box-shadow: none !important;
+ font-size: 0.95rem !important;
+ font-weight: 500;
+}
+
+.form-floating > textarea.form-control[style*="display: none"] + .note-editor + label.required::after
+{
+ content: "" !important;
+}
+
+.form-floating > textarea.form-control[style*="display: none"] + .note-editor + label .summernote-required-star
+{
+ display: inline-block;
+ margin-left: 0.2rem;
+ color: #cc1d24;
+ opacity: 1;
+ visibility: visible;
+}
+
+.form-floating > textarea.form-control[style*="display: none"] + .note-editor + label.required.summernote-has-content .summernote-required-star,
+.form-floating > textarea.form-control[style*="display: none"] + .note-editor + label.required.summernote-focused .summernote-required-star
+{
+ opacity: 0;
+ visibility: hidden;
+}
diff --git a/src/helper/ButtonRounded.php b/src/helper/ButtonRounded.php
new file mode 100644
index 0000000..0149c16
--- /dev/null
+++ b/src/helper/ButtonRounded.php
@@ -0,0 +1,35 @@
+getForm(false);
+
+ if(!$form instanceof Form)
+ {
+ return '';
+ }
+
+ $userClass = trim((string) $this->getControl()->getAttribute('class'));
+
+ if($userClass !== '')
+ {
+ return '';
+ }
+
+ $class = $form->getButtonClass();
+
+ return $class !== null && $class !== '' ? ' ' . $class : '';
+ }
+}
diff --git a/src/helper/CoreList.php b/src/helper/CoreList.php
index 635b4c3..1b07a24 100644
--- a/src/helper/CoreList.php
+++ b/src/helper/CoreList.php
@@ -4,7 +4,11 @@
namespace ModulIS\Form\Helper;
+use Kravcik\LatteFontAwesomeIcon\Extension;
+use ModulIS\Form\Control\Signalable;
+use ModulIS\Form\Form;
use Nette\Utils\Html;
+use function assert;
trait CoreList
{
@@ -22,7 +26,7 @@ public function getCoreControl(): Html|string
$inputs = null;
$form = $this->getForm();
- \assert($form instanceof \ModulIS\Form\Form);
+ assert($form instanceof Form);
foreach($this->getItems() as $key => $input)
{
@@ -113,23 +117,23 @@ public function renderItem(string|int $itemName)
$tooltip = Html::el('span')
->title($this->tooltips[$itemName])
->addAttributes(['data-bs-placement' => 'top', 'data-bs-toggle' => 'tooltip', 'data-bs-html' => 'true'])
- ->addHtml(\Kravcik\LatteFontAwesomeIcon\Extension::render('question-circle', color: 'blue'));
+ ->addHtml(Extension::render('question-circle', color: 'blue'));
}
- $class = 'form-check-inline mr-0 col-' . 12 / $this->itemsPerRow;
+ $class = 'form-check col-' . (12 / $this->itemsPerRow);
if($this->itemClass)
{
$class .= ' ' . $this->itemClass;
}
- if($this instanceof \ModulIS\Form\Control\Signalable && $this->hasSignal())
+ if($this instanceof Signalable && $this->hasSignal())
{
$this->addSignalsToInput($input);
}
return Html::el('div')
- ->class(($this->toggleButton ? 'p-0 ' : 'form-check ') . $class)
+ ->class(($this->toggleButton ? 'p-0 ' : '') . $class)
->addHtml($input . $label . $tooltip);
}
diff --git a/src/helper/InputCoreControl.php b/src/helper/InputCoreControl.php
index 85e6e93..789ffa9 100644
--- a/src/helper/InputCoreControl.php
+++ b/src/helper/InputCoreControl.php
@@ -26,8 +26,12 @@ public function getCoreControl()
$hasValidationClass = $this->getValidationClass() && $this->hasErrors() ? ' has-validation' : null;
+ $quickCopyHtml = $this instanceof QuickCopyable && $this->getQuickCopy()
+ ? $this->getQuickCopyButton()
+ : null;
+
return Html::el('div')
->class('input-group' . $hasValidationClass)
- ->addHtml($this->getPrepend() . $input . $this->getAppend() . $validationFeedBack);
+ ->addHtml($this->getPrepend() . $input . $this->getAppend() . $quickCopyHtml . $validationFeedBack);
}
}
diff --git a/src/helper/InputGroup.php b/src/helper/InputGroup.php
index 08a142f..3e2f14c 100644
--- a/src/helper/InputGroup.php
+++ b/src/helper/InputGroup.php
@@ -23,17 +23,6 @@ public function getPrepend(): ?Html
->class('input-group-text' . ($this->prependClass ? ' ' . $this->prependClass : null))
->addHtml($this->prepend);
- if(!empty($this->renderFloating) && $this->tooltip)
- {
- $tooltip = Html::el('span')
- ->title($this->tooltip)
- ->class('input-group-text')
- ->addAttributes(['data-bs-placement' => 'right', 'data-bs-toggle' => 'tooltip', 'data-bs-html' => 'true'])
- ->addHtml(\Kravcik\LatteFontAwesomeIcon\Extension::render('question-circle', color: 'blue'));
-
- return Html::el()->addHtml($this->prepend ? $tooltip . $prepend : $tooltip);
- }
-
if(!$this->prepend)
{
return null;
diff --git a/src/helper/Label.php b/src/helper/Label.php
index f332343..f658c20 100644
--- a/src/helper/Label.php
+++ b/src/helper/Label.php
@@ -4,7 +4,10 @@
namespace ModulIS\Form\Helper;
+use Kravcik\LatteFontAwesomeIcon\Extension;
+use ModulIS\Form\Form;
use Nette\Utils\Html;
+use function assert;
trait Label
{
@@ -22,8 +25,26 @@ public function getCoreLabel()
$tooltip = Html::el('span')
->title($this->tooltip)
->addAttributes(['data-bs-placement' => 'right', 'data-bs-toggle' => 'tooltip', 'data-bs-html' => 'true'])
- ->addHtml(\Kravcik\LatteFontAwesomeIcon\Extension::render('question-circle', color: 'blue'));
+ ->addHtml(Extension::render('question-circle', color: 'blue'));
- return !empty($this->renderFloating) ? $label : $label . $tooltip;
+ $form = $this->getForm();
+ assert($form instanceof Form);
+
+ if($form->getRenderFloating() === true)
+ {
+ $label->addHtml(' ');
+ if($this->isRequired())
+ {
+ $label->addHtml(Html::el('span')
+ ->class('required-inline-star')
+ ->setText('★'));
+ $label->class($label->getAttribute('class') . ' has-inline-required-star');
+ }
+ $label->addHtml($tooltip);
+
+ return $label;
+ }
+
+ return $label . $tooltip;
}
}
diff --git a/src/helper/QuickCopy.php b/src/helper/QuickCopy.php
new file mode 100644
index 0000000..b1e3c54
--- /dev/null
+++ b/src/helper/QuickCopy.php
@@ -0,0 +1,48 @@
+quickCopy = $value;
+
+ return $this;
+ }
+
+
+ public function getQuickCopy(): bool
+ {
+ return $this->quickCopy;
+ }
+
+
+ public function getQuickCopyButton(): Html
+ {
+ return Html::el('span')
+ ->class('input-group-text quick-copy-wrap')
+ ->addHtml(
+ Html::el('button')
+ ->type('button')
+ ->class('btn quick-copy-btn')
+ ->setAttribute('title', 'Zkopírovat do schránky')
+ ->addHtml(Html::el('i')->class('fal fa-copy fa-fw'))
+ );
+ }
+}
diff --git a/src/helper/RenderFloating.php b/src/helper/RenderFloating.php
index 2db1bcc..3b15792 100644
--- a/src/helper/RenderFloating.php
+++ b/src/helper/RenderFloating.php
@@ -50,9 +50,13 @@ public function renderFloating(): Html
->class('form-floating')
->addHtml($input . $label . $validationFeedBack);
+ $quickCopyHtml = $this instanceof QuickCopyable && $this->getQuickCopy()
+ ? $this->getQuickCopyButton()
+ : null;
+
$inputGroup = Html::el('div')
->class('input-group')
- ->addHtml($this->getPrepend() . $floatingDiv . $this->getAppend());
+ ->addHtml($this->getPrepend() . $floatingDiv . $this->getAppend() . $quickCopyHtml);
return Html::el('div')
->class($wrapClass)
diff --git a/src/helper/WrapControl.php b/src/helper/WrapControl.php
index e839d2e..230dd3d 100644
--- a/src/helper/WrapControl.php
+++ b/src/helper/WrapControl.php
@@ -4,7 +4,9 @@
namespace ModulIS\Form\Helper;
+use ModulIS\Form\Form;
use Nette\Utils\Html;
+use function assert;
trait WrapControl
{
@@ -76,7 +78,7 @@ public function getWrapControl(): Html
if(!$this->wrapControl)
{
$form = $this->getForm();
- \assert($form instanceof \ModulIS\Form\Form);
+ assert($form instanceof Form);
$this->wrapControl = Html::el('div')
->class($form->getDefaultInputWrapClass());
@@ -89,7 +91,7 @@ public function getWrapControl(): Html
public function renderWrap(): Html
{
$form = $this->getForm();
- \assert($form instanceof \ModulIS\Form\Form);
+ assert($form instanceof Form);
$label = $this->getCoreLabel();
$input = $this->getCoreControl();
@@ -97,7 +99,7 @@ public function renderWrap(): Html
$inputClass = 'align-self-center';
$labelClass = 'align-self-center';
- if($this->getRenderInline() ?? $form->getRenderInline())
+ if(($this->getRenderInline() ?? $form->getRenderInline()) === true)
{
$inputClass .= $this->inputClass ? ' ' . $this->inputClass : ' col-sm-12';
$labelClass .= $this->labelClass ? ' ' . $this->labelClass : ' col-sm-12';
diff --git a/src/js/form.js b/src/js/form.js
index eabe906..c56ea23 100644
--- a/src/js/form.js
+++ b/src/js/form.js
@@ -1,16 +1,18 @@
+import naja from 'naja';
+
Nette.validators.CodeComponentFormValidator_greater = function(elem, args, val)
{
- return parseInt(val) > parseInt(args);
+ return parseInt(val) > parseInt(args);
};
Nette.validators.CodeComponentFormValidator_less = function(elem, args, val)
{
- return parseInt(val) < parseInt(args);
+ return parseInt(val) < parseInt(args);
};
Nette.validators.CodeComponentFormValidator_sameLength = function(elem, args, val)
{
- return args.length === val.length;
+ return args.length === val.length;
};
async function inputSignal(input, url, event)
@@ -137,11 +139,11 @@ function registerAutocomplete(element)
}, 900);
if(typeof varUrlOnSelect !== 'undefined')
- {
+ {
let form = jqueryElement.closest('form');
naja.makeRequest('GET', varUrlOnSelect, {selected: item.data, formdata: form.serialize()});
- }
+ }
},
fetch: function(text, callback)
{
@@ -242,6 +244,104 @@ function formatSelectData(data)
return image;
};
+function summernoteIsEmpty(noteEditable)
+{
+ if(!noteEditable)
+ {
+ return true;
+ }
+
+ let html = (noteEditable.innerHTML || '').trim().toLowerCase();
+
+ if(html === '' || html === '
' || html === '