@nanahoshi/mona-ui 0.24.2 → 0.24.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +2 -0
  2. package/fesm2022/nanahoshi-mona-ui-auto-complete.mjs +1 -1
  3. package/fesm2022/nanahoshi-mona-ui-check-box.mjs +1 -1
  4. package/fesm2022/nanahoshi-mona-ui-color-gradient.mjs +1 -1
  5. package/fesm2022/nanahoshi-mona-ui-color-picker.mjs +1 -1
  6. package/fesm2022/nanahoshi-mona-ui-combo-box.mjs +4 -2
  7. package/fesm2022/nanahoshi-mona-ui-date-input.mjs +1 -1
  8. package/fesm2022/nanahoshi-mona-ui-date-picker.mjs +8 -4
  9. package/fesm2022/nanahoshi-mona-ui-datetime-picker.mjs +8 -4
  10. package/fesm2022/nanahoshi-mona-ui-dropdown-list.mjs +4 -2
  11. package/fesm2022/nanahoshi-mona-ui-dropdowns.mjs +13 -3
  12. package/fesm2022/nanahoshi-mona-ui-editor.mjs +1 -1
  13. package/fesm2022/nanahoshi-mona-ui-filter.mjs +1 -1
  14. package/fesm2022/{nanahoshi-mona-ui-grid-grid-footer-row.component-x_euoNnS.mjs → nanahoshi-mona-ui-grid-grid-footer-row.component-DLOPRuFv.mjs} +2 -2
  15. package/fesm2022/{nanahoshi-mona-ui-grid-grid-list.component-C1_6vq77.mjs → nanahoshi-mona-ui-grid-grid-list.component-Dw5U9H0a.mjs} +4 -4
  16. package/fesm2022/{nanahoshi-mona-ui-grid-grid-row.component-BhukVWIy.mjs → nanahoshi-mona-ui-grid-grid-row.component-D1kyAzD8.mjs} +3 -3
  17. package/fesm2022/{nanahoshi-mona-ui-grid-grid-virtual-list.component-XQKOPPVs.mjs → nanahoshi-mona-ui-grid-grid-virtual-list.component-ECK1_smx.mjs} +4 -4
  18. package/fesm2022/{nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-Iw-wvKGl.mjs → nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-D4lUDbfx.mjs} +10 -15
  19. package/fesm2022/nanahoshi-mona-ui-grid.mjs +1 -1
  20. package/fesm2022/nanahoshi-mona-ui-internal-filter-input.mjs +2 -2
  21. package/fesm2022/nanahoshi-mona-ui-internal-tree.mjs +1 -1
  22. package/fesm2022/nanahoshi-mona-ui-internal.mjs +15 -1
  23. package/fesm2022/nanahoshi-mona-ui-menubar.mjs +3 -3
  24. package/fesm2022/nanahoshi-mona-ui-multi-select.mjs +1 -1
  25. package/fesm2022/nanahoshi-mona-ui-numeric-text-box.mjs +1 -1
  26. package/fesm2022/nanahoshi-mona-ui-otp-input.mjs +1 -1
  27. package/fesm2022/nanahoshi-mona-ui-popup-menu.mjs +2 -2
  28. package/fesm2022/nanahoshi-mona-ui-popup.mjs +269 -182
  29. package/fesm2022/nanahoshi-mona-ui-switch.mjs +1 -1
  30. package/fesm2022/nanahoshi-mona-ui-text-box.mjs +95 -58
  31. package/fesm2022/nanahoshi-mona-ui-time-picker.mjs +7 -5
  32. package/fesm2022/nanahoshi-mona-ui-time-selector.mjs +1 -1
  33. package/package.json +1 -1
  34. package/types/nanahoshi-mona-ui-dropdowns.d.ts +6 -0
  35. package/types/nanahoshi-mona-ui-internal.d.ts +9 -1
  36. package/types/nanahoshi-mona-ui-popup.d.ts +97 -36
  37. package/types/nanahoshi-mona-ui-text-box.d.ts +62 -14
@@ -75,7 +75,7 @@ const switchThemeVariants = cva(`
75
75
  small: "rounded-sm"
76
76
  },
77
77
  size: {
78
- large: "h-7.5 w-14 text-md",
78
+ large: "h-7.5 w-14 text-base",
79
79
  medium: "h-6.5 w-12 text-sm",
80
80
  small: "h-5.5 w-10 text-xs"
81
81
  }
@@ -1,12 +1,10 @@
1
1
  import { NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { Directive, inject, DestroyRef, computed, viewChild, contentChildren, TemplateRef, input, output, model, Component } from '@angular/core';
4
- import * as i1 from '@angular/forms';
5
- import { FormsModule } from '@angular/forms';
3
+ import { Directive, inject, ElementRef, computed, viewChild, contentChildren, TemplateRef, input, booleanAttribute, output, model, Component } from '@angular/core';
6
4
  import { LucideX } from '@lucide/angular';
7
5
  import { ButtonDirective } from '@nanahoshi/mona-ui/button';
8
6
  import { MonaI18nService } from '@nanahoshi/mona-ui/i18n';
9
- import { themeControlSurfaceClasses, rxTimeout, AttributeBinderDirective } from '@nanahoshi/mona-ui/internal';
7
+ import { themeControlSurfaceClasses, createElementControlId, AttributeBinderDirective } from '@nanahoshi/mona-ui/internal';
10
8
  import { twMerge } from 'tailwind-merge';
11
9
  import { cva } from 'class-variance-authority';
12
10
 
@@ -78,7 +76,7 @@ const textBoxThemeVariants = cva(`
78
76
  small: "rounded-sm"
79
77
  },
80
78
  size: {
81
- large: "h-10 text-md",
79
+ large: "h-10 text-base",
82
80
  medium: "h-9 text-sm",
83
81
  small: "h-8 text-xs"
84
82
  }
@@ -119,33 +117,18 @@ const inputThemeVariants = cva(`
119
117
  small: "rounded-sm"
120
118
  },
121
119
  size: {
122
- large: "h-10 text-md",
120
+ large: "h-10 text-base",
123
121
  medium: "h-9 text-sm",
124
122
  small: "h-8 text-xs"
125
123
  }
126
- },
127
- compoundVariants: [
128
- {
129
- rounded: "full",
130
- size: "large",
131
- class: "px-4"
132
- },
133
- {
134
- rounded: "full",
135
- size: "medium",
136
- class: "px-4"
137
- },
138
- {
139
- rounded: "full",
140
- size: "small",
141
- class: "px-4"
142
- }
143
- ]
124
+ }
144
125
  });
145
126
 
127
+ /** Input types that support `setSelectionRange`; `email` throws. */
128
+ const selectableInputTypes = new Set(["password", "search", "tel", "text", "url"]);
146
129
  class TextBoxComponent {
147
130
  constructor() {
148
- this.#destroyRef = inject(DestroyRef);
131
+ this.#hostElementRef = inject(ElementRef);
149
132
  this.#i18n = inject(MonaI18nService);
150
133
  this.classes = computed(() => {
151
134
  const rounded = this.rounded();
@@ -161,19 +144,36 @@ class TextBoxComponent {
161
144
  ...(ngDevMode ? [{ debugName: "invalidInput" }] : /* istanbul ignore next */ []));
162
145
  this.messages = this.#i18n.componentMessages("textBox", TEXT_BOX_DEFAULT_MESSAGES);
163
146
  this.prefixTemplateList = contentChildren(TextBoxPrefixTemplateDirective, { ...(ngDevMode ? { debugName: "prefixTemplateList" } : /* istanbul ignore next */ {}), read: TemplateRef });
147
+ this.showClearButton = computed(() => this.clearButton() && !this.disabled() && !this.readonly() && !!this.value(), /* @ts-ignore */
148
+ ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
164
149
  this.suffixTemplateList = contentChildren(TextBoxSuffixTemplateDirective, { ...(ngDevMode ? { debugName: "suffixTemplateList" } : /* istanbul ignore next */ {}), read: TemplateRef });
150
+ /**
151
+ * @description Id of an element that describes the inner input, such as a hint or an error message.
152
+ * @default ""
153
+ */
154
+ this.ariaDescribedBy = input("", { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: "aria-describedby" });
155
+ /**
156
+ * @description Accessible name of the inner input. Prefer a visible label, linked through {@link inputId} or
157
+ * {@link ariaLabelledBy}; the placeholder is not a label.
158
+ * @default ""
159
+ */
160
+ this.ariaLabel = input("", { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: "aria-label" });
161
+ /**
162
+ * @description Id of the element that labels the inner input.
163
+ * @default ""
164
+ */
165
+ this.ariaLabelledBy = input("", { ...(ngDevMode ? { debugName: "ariaLabelledBy" } : /* istanbul ignore next */ {}), alias: "aria-labelledby" });
165
166
  /**
166
167
  * @description Displays a clear button that resets the value to empty.
168
+ * The button is hidden while the value is empty or the text box is disabled or read-only.
167
169
  * @default false
168
170
  */
169
- this.clearButton = input(false, /* @ts-ignore */
170
- ...(ngDevMode ? [{ debugName: "clearButton" }] : /* istanbul ignore next */ []));
171
+ this.clearButton = input(false, { ...(ngDevMode ? { debugName: "clearButton" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
171
172
  /**
172
173
  * @description Renders the component with reduced visual emphasis and removes pointer interaction.
173
174
  * @default false
174
175
  */
175
- this.disabled = input(false, /* @ts-ignore */
176
- ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
176
+ this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
177
177
  /**
178
178
  * @description Additional HTML attributes applied directly to the inner `<input>` element.
179
179
  * @default {}
@@ -181,7 +181,8 @@ class TextBoxComponent {
181
181
  this.inputAttributes = input({}, /* @ts-ignore */
182
182
  ...(ngDevMode ? [{ debugName: "inputAttributes" }] : /* istanbul ignore next */ []));
183
183
  /**
184
- * @description Emitted when the inner input loses focus.
184
+ * @description Emitted when the inner input loses focus, including when focus moves to the clear button or to
185
+ * content in the prefix or suffix templates.
185
186
  */
186
187
  this.inputBlur = output();
187
188
  /**
@@ -194,6 +195,13 @@ class TextBoxComponent {
194
195
  * @description Emitted when the inner input gains focus.
195
196
  */
196
197
  this.inputFocus = output();
198
+ /**
199
+ * @description Id of the inner `<input>` element. Point a `<label for>` or `<mona-label for>` at it to label the
200
+ * text box. A unique id is generated when it isn't set.
201
+ * @default generated
202
+ */
203
+ this.inputId = input(createElementControlId(), /* @ts-ignore */
204
+ ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
197
205
  /**
198
206
  * @description Inline styles applied to the inner `<input>` element.
199
207
  * Accepts a style string or a `CSSStyleDeclaration` partial.
@@ -206,8 +214,28 @@ class TextBoxComponent {
206
214
  * this is written by the `FormField` directive.
207
215
  * @default false
208
216
  */
209
- this.invalid = input(false, /* @ts-ignore */
210
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
217
+ this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
218
+ /**
219
+ * @description Maximum number of characters, set as `maxlength` on the inner input. When bound to a signal form
220
+ * field via `[formField]`, this is written from the field's `maxLength` rule.
221
+ * @default undefined
222
+ */
223
+ this.maxLength = input(undefined, /* @ts-ignore */
224
+ ...(ngDevMode ? [{ debugName: "maxLength" }] : /* istanbul ignore next */ []));
225
+ /**
226
+ * @description Minimum number of characters, set as `minlength` on the inner input. When bound to a signal form
227
+ * field via `[formField]`, this is written from the field's `minLength` rule.
228
+ * @default undefined
229
+ */
230
+ this.minLength = input(undefined, /* @ts-ignore */
231
+ ...(ngDevMode ? [{ debugName: "minLength" }] : /* istanbul ignore next */ []));
232
+ /**
233
+ * @description Name of the inner input, used by browsers and password managers for autofill. When bound to a
234
+ * signal form field via `[formField]`, this is written by the `FormField` directive.
235
+ * @default ""
236
+ */
237
+ this.name = input("", /* @ts-ignore */
238
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
211
239
  /**
212
240
  * @description Placeholder text shown when no value has been entered.
213
241
  * @default ""
@@ -218,15 +246,13 @@ class TextBoxComponent {
218
246
  * @description Prevents value changes while preserving the component's visual state.
219
247
  * @default false
220
248
  */
221
- this.readonly = input(false, /* @ts-ignore */
222
- ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
249
+ this.readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
223
250
  /**
224
251
  * @description Marks the text box as required.
225
252
  * Triggers the invalid state when the value is empty and the field has been touched.
226
253
  * @default false
227
254
  */
228
- this.required = input(false, /* @ts-ignore */
229
- ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
255
+ this.required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
230
256
  /**
231
257
  * @description Border-radius preset applied to the component.
232
258
  * @default "medium"
@@ -240,8 +266,8 @@ class TextBoxComponent {
240
266
  this.size = input("medium", /* @ts-ignore */
241
267
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
242
268
  /**
243
- * @description Emitted when the text box is interacted with — on blur, value change, or clear.
244
- * The `FormField` directive listens to this to mark the field as touched.
269
+ * @description Emitted when focus leaves the text box, including its clear button and prefix and suffix content.
270
+ * Form directives (`[formField]`, `ngModel`, `formControl`) listen to this to mark the field as touched.
245
271
  */
246
272
  this.touch = output();
247
273
  /**
@@ -249,8 +275,7 @@ class TextBoxComponent {
249
275
  * this is written by the `FormField` directive.
250
276
  * @default false
251
277
  */
252
- this.touched = input(false, /* @ts-ignore */
253
- ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
278
+ this.touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
254
279
  /**
255
280
  * @description Sets the `type` attribute of the inner `<input>` element.
256
281
  * @default "text"
@@ -269,41 +294,55 @@ class TextBoxComponent {
269
294
  this.value = model("", /* @ts-ignore */
270
295
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
271
296
  }
272
- #destroyRef;
297
+ #hostElementRef;
273
298
  #i18n;
274
- focus() {
275
- const el = this.inputRef().nativeElement;
276
- el.focus();
277
- rxTimeout(this.#destroyRef, () => {
278
- el.scrollLeft = el.scrollWidth;
279
- });
299
+ /**
300
+ * Focuses the inner input and places the caret after the value, so the end of a long value is in view.
301
+ * @param options Options passed to the native `focus()` call, such as `preventScroll`.
302
+ */
303
+ focus(options) {
304
+ const input = this.inputRef().nativeElement;
305
+ input.focus(options);
306
+ if (selectableInputTypes.has(input.type)) {
307
+ const end = input.value.length;
308
+ input.setSelectionRange(end, end);
309
+ }
280
310
  }
281
311
  onClearClick() {
282
312
  this.value.set("");
283
- this.touch.emit();
284
313
  this.inputRef().nativeElement.focus();
285
314
  }
286
315
  onInputBlur(event) {
287
316
  this.inputBlur.emit(event);
288
- this.touch.emit();
289
317
  }
290
318
  onValueChange(value) {
291
319
  this.value.set(value);
320
+ }
321
+ onFocusOut(event) {
322
+ const nextTarget = event.relatedTarget;
323
+ if (nextTarget instanceof Node && this.#hostElementRef.nativeElement.contains(nextTarget)) {
324
+ return;
325
+ }
292
326
  this.touch.emit();
293
327
  }
294
328
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TextBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
295
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: TextBoxComponent, isStandalone: true, selector: "mona-text-box", inputs: { clearButton: { classPropertyName: "clearButton", publicName: "clearButton", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, inputAttributes: { classPropertyName: "inputAttributes", publicName: "inputAttributes", isSignal: true, isRequired: false, transformFunction: null }, inputClass: { classPropertyName: "inputClass", publicName: "inputClass", isSignal: true, isRequired: false, transformFunction: null }, inputStyle: { classPropertyName: "inputStyle", publicName: "inputStyle", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { inputBlur: "inputBlur", inputFocus: "inputFocus", touch: "touch", value: "valueChange" }, host: { properties: { "attr.data-disabled": "disabled() || null", "attr.data-readonly": "readonly() || null", "attr.data-required": "required() || null", "attr.data-invalid": "invalidInput() || null", "class": "classes()" } }, queries: [{ propertyName: "prefixTemplateList", predicate: TextBoxPrefixTemplateDirective, read: TemplateRef, isSignal: true }, { propertyName: "suffixTemplateList", predicate: TextBoxSuffixTemplateDirective, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (prefixTemplate of prefixTemplateList(); track $index) {\n <span class=\"flex h-full flex-wrap items-center\">\n <ng-container *ngTemplateOutlet=\"prefixTemplate\"></ng-container>\n </span>\n}\n<input\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [placeholder]=\"placeholder()\"\n [type]=\"type()\"\n [class]=\"inputClass()\"\n [style]=\"inputStyle()\"\n [attr.aria-invalid]=\"invalidInput() ? 'true' : null\"\n [monaAttributeBinder]=\"inputAttributes()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onValueChange($event)\"\n (blur)=\"onInputBlur($event)\"\n (focus)=\"inputFocus.emit($event)\"\n #input />\n\n@let textBoxValue = value();\n@if (clearButton() && textBoxValue && textBoxValue.length !== 0) {\n <button monaButton look=\"link\" size=\"small\" class=\"me-2 p-0\" [attr.aria-label]=\"messages().clear\" (click)=\"onClearClick()\">\n <svg lucideX class=\"size-4 opacity-50 hover:opacity-90\"></svg>\n </button>\n}\n\n@for (suffixTemplate of suffixTemplateList(); track $index) {\n <span class=\"flex h-full flex-wrap items-center\">\n <ng-container *ngTemplateOutlet=\"suffixTemplate\"></ng-container>\n </span>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: ButtonDirective, selector: "button[monaButton]", inputs: ["aria-haspopup", "disabled", "iconOnly", "loading", "look", "rounded", "selected", "size", "tabindex", "toggleable", "type", "class"], outputs: ["lookChange", "selectedChange"] }, { kind: "directive", type: AttributeBinderDirective, selector: "[monaAttributeBinder]", inputs: ["monaAttributeBinder"] }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }] }); }
329
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: TextBoxComponent, isStandalone: true, selector: "mona-text-box", inputs: { ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, clearButton: { classPropertyName: "clearButton", publicName: "clearButton", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, inputAttributes: { classPropertyName: "inputAttributes", publicName: "inputAttributes", isSignal: true, isRequired: false, transformFunction: null }, inputClass: { classPropertyName: "inputClass", publicName: "inputClass", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, inputStyle: { classPropertyName: "inputStyle", publicName: "inputStyle", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { inputBlur: "inputBlur", inputFocus: "inputFocus", touch: "touch", value: "valueChange" }, host: { listeners: { "focusout": "onFocusOut($event)" }, properties: { "attr.aria-describedby": "null", "attr.aria-label": "null", "attr.aria-labelledby": "null", "attr.data-disabled": "disabled() || null", "attr.data-readonly": "readonly() || null", "attr.data-required": "required() || null", "attr.data-invalid": "invalidInput() || null", "class": "classes()" } }, queries: [{ propertyName: "prefixTemplateList", predicate: TextBoxPrefixTemplateDirective, read: TemplateRef, isSignal: true }, { propertyName: "suffixTemplateList", predicate: TextBoxSuffixTemplateDirective, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (prefixTemplate of prefixTemplateList(); track $index) {\n <span class=\"flex h-full flex-wrap items-center\">\n <ng-container *ngTemplateOutlet=\"prefixTemplate\"></ng-container>\n </span>\n}\n<input\n [id]=\"inputId()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [placeholder]=\"placeholder()\"\n [type]=\"type()\"\n [class]=\"inputClass()\"\n [style]=\"inputStyle()\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n [attr.aria-invalid]=\"invalidInput() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.name]=\"name() || null\"\n [monaAttributeBinder]=\"inputAttributes()\"\n [value]=\"value()\"\n (input)=\"onValueChange(input.value)\"\n (blur)=\"onInputBlur($event)\"\n (focus)=\"inputFocus.emit($event)\"\n #input />\n\n@if (showClearButton()) {\n <button\n monaButton\n look=\"ghost\"\n size=\"small\"\n class=\"me-1 size-6 shrink-0 p-0 text-muted-foreground hover:text-foreground\"\n [attr.aria-label]=\"messages().clear\"\n (click)=\"onClearClick()\">\n <svg lucideX class=\"size-4\"></svg>\n </button>\n}\n\n@for (suffixTemplate of suffixTemplateList(); track $index) {\n <span class=\"flex h-full flex-wrap items-center\">\n <ng-container *ngTemplateOutlet=\"suffixTemplate\"></ng-container>\n </span>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: ButtonDirective, selector: "button[monaButton]", inputs: ["aria-haspopup", "disabled", "iconOnly", "loading", "look", "rounded", "selected", "size", "tabindex", "toggleable", "type", "class"], outputs: ["lookChange", "selectedChange"] }, { kind: "directive", type: AttributeBinderDirective, selector: "[monaAttributeBinder]", inputs: ["monaAttributeBinder"] }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }] }); }
296
330
  }
297
331
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TextBoxComponent, decorators: [{
298
332
  type: Component,
299
- args: [{ selector: "mona-text-box", imports: [NgTemplateOutlet, FormsModule, ButtonDirective, AttributeBinderDirective, LucideX], host: {
333
+ args: [{ selector: "mona-text-box", imports: [NgTemplateOutlet, ButtonDirective, AttributeBinderDirective, LucideX], host: {
334
+ // The ARIA inputs below are applied to the inner input, so the host must not keep the static attributes.
335
+ "[attr.aria-describedby]": "null",
336
+ "[attr.aria-label]": "null",
337
+ "[attr.aria-labelledby]": "null",
300
338
  "[attr.data-disabled]": "disabled() || null",
301
339
  "[attr.data-readonly]": "readonly() || null",
302
340
  "[attr.data-required]": "required() || null",
303
341
  "[attr.data-invalid]": "invalidInput() || null",
304
- "[class]": "classes()"
305
- }, template: "@for (prefixTemplate of prefixTemplateList(); track $index) {\n <span class=\"flex h-full flex-wrap items-center\">\n <ng-container *ngTemplateOutlet=\"prefixTemplate\"></ng-container>\n </span>\n}\n<input\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [placeholder]=\"placeholder()\"\n [type]=\"type()\"\n [class]=\"inputClass()\"\n [style]=\"inputStyle()\"\n [attr.aria-invalid]=\"invalidInput() ? 'true' : null\"\n [monaAttributeBinder]=\"inputAttributes()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onValueChange($event)\"\n (blur)=\"onInputBlur($event)\"\n (focus)=\"inputFocus.emit($event)\"\n #input />\n\n@let textBoxValue = value();\n@if (clearButton() && textBoxValue && textBoxValue.length !== 0) {\n <button monaButton look=\"link\" size=\"small\" class=\"me-2 p-0\" [attr.aria-label]=\"messages().clear\" (click)=\"onClearClick()\">\n <svg lucideX class=\"size-4 opacity-50 hover:opacity-90\"></svg>\n </button>\n}\n\n@for (suffixTemplate of suffixTemplateList(); track $index) {\n <span class=\"flex h-full flex-wrap items-center\">\n <ng-container *ngTemplateOutlet=\"suffixTemplate\"></ng-container>\n </span>\n}\n" }]
306
- }], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ["input", { isSignal: true }] }], prefixTemplateList: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TextBoxPrefixTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], suffixTemplateList: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TextBoxSuffixTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], clearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearButton", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], inputAttributes: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputAttributes", required: false }] }], inputBlur: [{ type: i0.Output, args: ["inputBlur"] }], inputClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputClass", required: false }] }], inputFocus: [{ type: i0.Output, args: ["inputFocus"] }], inputStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputStyle", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
342
+ "[class]": "classes()",
343
+ "(focusout)": "onFocusOut($event)"
344
+ }, template: "@for (prefixTemplate of prefixTemplateList(); track $index) {\n <span class=\"flex h-full flex-wrap items-center\">\n <ng-container *ngTemplateOutlet=\"prefixTemplate\"></ng-container>\n </span>\n}\n<input\n [id]=\"inputId()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [placeholder]=\"placeholder()\"\n [type]=\"type()\"\n [class]=\"inputClass()\"\n [style]=\"inputStyle()\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n [attr.aria-invalid]=\"invalidInput() ? 'true' : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.maxlength]=\"maxLength() ?? null\"\n [attr.minlength]=\"minLength() ?? null\"\n [attr.name]=\"name() || null\"\n [monaAttributeBinder]=\"inputAttributes()\"\n [value]=\"value()\"\n (input)=\"onValueChange(input.value)\"\n (blur)=\"onInputBlur($event)\"\n (focus)=\"inputFocus.emit($event)\"\n #input />\n\n@if (showClearButton()) {\n <button\n monaButton\n look=\"ghost\"\n size=\"small\"\n class=\"me-1 size-6 shrink-0 p-0 text-muted-foreground hover:text-foreground\"\n [attr.aria-label]=\"messages().clear\"\n (click)=\"onClearClick()\">\n <svg lucideX class=\"size-4\"></svg>\n </button>\n}\n\n@for (suffixTemplate of suffixTemplateList(); track $index) {\n <span class=\"flex h-full flex-wrap items-center\">\n <ng-container *ngTemplateOutlet=\"suffixTemplate\"></ng-container>\n </span>\n}\n" }]
345
+ }], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ["input", { isSignal: true }] }], prefixTemplateList: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TextBoxPrefixTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], suffixTemplateList: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TextBoxSuffixTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], clearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearButton", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], inputAttributes: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputAttributes", required: false }] }], inputBlur: [{ type: i0.Output, args: ["inputBlur"] }], inputClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputClass", required: false }] }], inputFocus: [{ type: i0.Output, args: ["inputFocus"] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], inputStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputStyle", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], minLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minLength", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
307
346
 
308
347
  class TextBoxDirective {
309
348
  constructor() {
@@ -321,8 +360,7 @@ class TextBoxDirective {
321
360
  * @description Marks the input as invalid, triggering error border and ring styling.
322
361
  * @default false
323
362
  */
324
- this.invalid = input(false, /* @ts-ignore */
325
- ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
363
+ this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
326
364
  /**
327
365
  * @description Border-radius preset applied to the component.
328
366
  * @default "medium"
@@ -339,8 +377,7 @@ class TextBoxDirective {
339
377
  * @description Marks the input as touched. Error styling is only shown when both `invalid` and `touched` are `true`.
340
378
  * @default false
341
379
  */
342
- this.touched = input(false, /* @ts-ignore */
343
- ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
380
+ this.touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
344
381
  /**
345
382
  * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
346
383
  * @default ""
@@ -11,7 +11,7 @@ import { themeControlSurfaceClasses, createElementControlId } from '@nanahoshi/m
11
11
  import { TextBoxComponent, TextBoxSuffixTemplateDirective } from '@nanahoshi/mona-ui/text-box';
12
12
  import { TimeSelectorComponent } from '@nanahoshi/mona-ui/time-selector';
13
13
  import { DateTime } from 'luxon';
14
- import { fromEvent } from 'rxjs';
14
+ import { fromEvent, filter } from 'rxjs';
15
15
  import { twMerge } from 'tailwind-merge';
16
16
  import { cva } from 'class-variance-authority';
17
17
 
@@ -63,7 +63,7 @@ const timePickerBaseThemeVariants = cva(`
63
63
  small: "rounded-sm"
64
64
  },
65
65
  size: {
66
- large: "h-10 text-md",
66
+ large: "h-10 text-base",
67
67
  medium: "h-9 text-sm",
68
68
  small: "h-8 text-xs"
69
69
  }
@@ -129,6 +129,8 @@ class TimePickerComponent {
129
129
  "aria-haspopup": hasPopup,
130
130
  "aria-invalid": invalid,
131
131
  id: this.#id,
132
+ // The text box hides its clear button when read-only, so typing is blocked here instead.
133
+ readonly: this.readonly() || this.readonlyInput(),
132
134
  role: "combobox"
133
135
  };
134
136
  }, /* @ts-ignore */
@@ -428,7 +430,7 @@ class TimePickerComponent {
428
430
  .pipe(takeUntilDestroyed(this.#destroyRef))
429
431
  .subscribe(() => this.focus());
430
432
  this.#dropdownService.popupCloseComplete$
431
- .pipe(takeUntilDestroyed(this.#destroyRef))
433
+ .pipe(filter(() => this.#dropdownService.focusCanReturn()), takeUntilDestroyed(this.#destroyRef))
432
434
  .subscribe(() => this.focus());
433
435
  }
434
436
  updateCurrentDateString(date, format) {
@@ -447,7 +449,7 @@ class TimePickerComponent {
447
449
  useExisting: forwardRef(() => TimePickerComponent),
448
450
  multi: false
449
451
  }
450
- ], viewQueries: [{ propertyName: "timePopupTemplateRef", first: true, predicate: ["timePopupTemplate"], descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.DropdownPopupHandlerDirective }], ngImport: i0, template: "<mona-text-box\n [disabled]=\"disabled()\"\n [readonly]=\"readonly() || readonlyInput()\"\n [required]=\"required()\"\n [value]=\"currentDateString()\"\n [inputAttributes]=\"inputAttributes()\"\n [invalid]=\"invalid()\"\n [placeholder]=\"placeholder()\"\n [rounded]=\"rounded()\"\n [size]=\"size()\"\n [touched]=\"touched()\"\n [clearButton]=\"showClearButton()\"\n (valueChange)=\"onDateStringEdit($event)\"\n (inputBlur)=\"onTimeInputBlur()\">\n <ng-template monaTextBoxSuffixTemplate>\n <button\n monaButton\n look=\"ghost\"\n [iconOnly]=\"true\"\n [size]=\"size()\"\n [rounded]=\"'none'\"\n (click)=\"onTimeInputButtonClick()\"\n class=\"aspect-square h-full w-auto\"\n [attr.aria-label]=\"messages().openTimePicker\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"popupId\"\n [disabled]=\"disabled() || readonly()\"\n [tabindex]=\"-1\">\n <svg lucideClock [size]=\"16\" aria-hidden=\"true\"></svg>\n </button>\n </ng-template>\n</mona-text-box>\n\n<ng-template #timePopupTemplate>\n <div\n [class]=\"pickerPopupClass()\"\n [id]=\"popupId\"\n aria-modal=\"true\"\n role=\"dialog\"\n [attr.aria-label]=\"messages().timePicker\"\n cdkTrapFocus>\n <mona-time-selector\n [value]=\"value()\"\n (valueChange)=\"onTimeSelectorValueChange($event)\"\n [max]=\"max()\"\n [min]=\"min()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [hourFormat]=\"hourFormat()\"\n [hourStep]=\"hourStep()\"\n [minuteStep]=\"minuteStep()\"\n [secondStep]=\"secondStep()\"\n [size]=\"size()\"\n [rounded]=\"rounded()\"\n [showSeconds]=\"showSeconds()\"></mona-time-selector>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: LucideClock, selector: "svg[lucideClock]" }, { kind: "directive", type: ButtonDirective, selector: "button[monaButton]", inputs: ["aria-haspopup", "disabled", "iconOnly", "loading", "look", "rounded", "selected", "size", "tabindex", "toggleable", "type", "class"], outputs: ["lookChange", "selectedChange"] }, { kind: "component", type: TimeSelectorComponent, selector: "mona-time-selector", inputs: ["aria-label", "disabled", "focusOnMount", "footer", "hourFormat", "hourStep", "invalid", "max", "min", "minuteStep", "readonly", "required", "rounded", "secondStep", "showSeconds", "size", "touched", "value"], outputs: ["touch", "valueChange"] }, { kind: "component", type: TextBoxComponent, selector: "mona-text-box", inputs: ["clearButton", "disabled", "inputAttributes", "inputClass", "inputStyle", "invalid", "placeholder", "readonly", "required", "rounded", "size", "touched", "type", "class", "value"], outputs: ["inputBlur", "inputFocus", "touch", "valueChange"] }, { kind: "directive", type: TextBoxSuffixTemplateDirective, selector: "ng-template[monaTextBoxSuffixTemplate]" }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }] }); }
452
+ ], viewQueries: [{ propertyName: "timePopupTemplateRef", first: true, predicate: ["timePopupTemplate"], descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.DropdownPopupHandlerDirective }], ngImport: i0, template: "<mona-text-box\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [value]=\"currentDateString()\"\n [inputAttributes]=\"inputAttributes()\"\n [invalid]=\"invalid()\"\n [placeholder]=\"placeholder()\"\n [rounded]=\"rounded()\"\n [size]=\"size()\"\n [touched]=\"touched()\"\n [clearButton]=\"showClearButton()\"\n (valueChange)=\"onDateStringEdit($event)\"\n (inputBlur)=\"onTimeInputBlur()\">\n <ng-template monaTextBoxSuffixTemplate>\n <button\n monaButton\n look=\"ghost\"\n [iconOnly]=\"true\"\n [size]=\"size()\"\n [rounded]=\"'none'\"\n (click)=\"onTimeInputButtonClick()\"\n class=\"aspect-square h-full w-auto\"\n [attr.aria-label]=\"messages().openTimePicker\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"popupId\"\n [disabled]=\"disabled() || readonly()\"\n [tabindex]=\"-1\">\n <svg lucideClock [size]=\"16\" aria-hidden=\"true\"></svg>\n </button>\n </ng-template>\n</mona-text-box>\n\n<ng-template #timePopupTemplate>\n <div\n [class]=\"pickerPopupClass()\"\n [id]=\"popupId\"\n aria-modal=\"true\"\n role=\"dialog\"\n [attr.aria-label]=\"messages().timePicker\"\n cdkTrapFocus>\n <mona-time-selector\n [value]=\"value()\"\n (valueChange)=\"onTimeSelectorValueChange($event)\"\n [max]=\"max()\"\n [min]=\"min()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [hourFormat]=\"hourFormat()\"\n [hourStep]=\"hourStep()\"\n [minuteStep]=\"minuteStep()\"\n [secondStep]=\"secondStep()\"\n [size]=\"size()\"\n [rounded]=\"rounded()\"\n [showSeconds]=\"showSeconds()\"></mona-time-selector>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: LucideClock, selector: "svg[lucideClock]" }, { kind: "directive", type: ButtonDirective, selector: "button[monaButton]", inputs: ["aria-haspopup", "disabled", "iconOnly", "loading", "look", "rounded", "selected", "size", "tabindex", "toggleable", "type", "class"], outputs: ["lookChange", "selectedChange"] }, { kind: "component", type: TimeSelectorComponent, selector: "mona-time-selector", inputs: ["aria-label", "disabled", "focusOnMount", "footer", "hourFormat", "hourStep", "invalid", "max", "min", "minuteStep", "readonly", "required", "rounded", "secondStep", "showSeconds", "size", "touched", "value"], outputs: ["touch", "valueChange"] }, { kind: "component", type: TextBoxComponent, selector: "mona-text-box", inputs: ["aria-describedby", "aria-label", "aria-labelledby", "clearButton", "disabled", "inputAttributes", "inputClass", "inputId", "inputStyle", "invalid", "maxLength", "minLength", "name", "placeholder", "readonly", "required", "rounded", "size", "touched", "type", "class", "value"], outputs: ["inputBlur", "inputFocus", "touch", "valueChange"] }, { kind: "directive", type: TextBoxSuffixTemplateDirective, selector: "ng-template[monaTextBoxSuffixTemplate]" }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }] }); }
451
453
  }
452
454
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TimePickerComponent, decorators: [{
453
455
  type: Component,
@@ -474,7 +476,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
474
476
  "[attr.data-readonly]": "readonly() || null",
475
477
  "[class]": "baseClass()",
476
478
  "(blur)": "onTimeInputBlur()"
477
- }, template: "<mona-text-box\n [disabled]=\"disabled()\"\n [readonly]=\"readonly() || readonlyInput()\"\n [required]=\"required()\"\n [value]=\"currentDateString()\"\n [inputAttributes]=\"inputAttributes()\"\n [invalid]=\"invalid()\"\n [placeholder]=\"placeholder()\"\n [rounded]=\"rounded()\"\n [size]=\"size()\"\n [touched]=\"touched()\"\n [clearButton]=\"showClearButton()\"\n (valueChange)=\"onDateStringEdit($event)\"\n (inputBlur)=\"onTimeInputBlur()\">\n <ng-template monaTextBoxSuffixTemplate>\n <button\n monaButton\n look=\"ghost\"\n [iconOnly]=\"true\"\n [size]=\"size()\"\n [rounded]=\"'none'\"\n (click)=\"onTimeInputButtonClick()\"\n class=\"aspect-square h-full w-auto\"\n [attr.aria-label]=\"messages().openTimePicker\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"popupId\"\n [disabled]=\"disabled() || readonly()\"\n [tabindex]=\"-1\">\n <svg lucideClock [size]=\"16\" aria-hidden=\"true\"></svg>\n </button>\n </ng-template>\n</mona-text-box>\n\n<ng-template #timePopupTemplate>\n <div\n [class]=\"pickerPopupClass()\"\n [id]=\"popupId\"\n aria-modal=\"true\"\n role=\"dialog\"\n [attr.aria-label]=\"messages().timePicker\"\n cdkTrapFocus>\n <mona-time-selector\n [value]=\"value()\"\n (valueChange)=\"onTimeSelectorValueChange($event)\"\n [max]=\"max()\"\n [min]=\"min()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [hourFormat]=\"hourFormat()\"\n [hourStep]=\"hourStep()\"\n [minuteStep]=\"minuteStep()\"\n [secondStep]=\"secondStep()\"\n [size]=\"size()\"\n [rounded]=\"rounded()\"\n [showSeconds]=\"showSeconds()\"></mona-time-selector>\n </div>\n</ng-template>\n" }]
479
+ }, template: "<mona-text-box\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [required]=\"required()\"\n [value]=\"currentDateString()\"\n [inputAttributes]=\"inputAttributes()\"\n [invalid]=\"invalid()\"\n [placeholder]=\"placeholder()\"\n [rounded]=\"rounded()\"\n [size]=\"size()\"\n [touched]=\"touched()\"\n [clearButton]=\"showClearButton()\"\n (valueChange)=\"onDateStringEdit($event)\"\n (inputBlur)=\"onTimeInputBlur()\">\n <ng-template monaTextBoxSuffixTemplate>\n <button\n monaButton\n look=\"ghost\"\n [iconOnly]=\"true\"\n [size]=\"size()\"\n [rounded]=\"'none'\"\n (click)=\"onTimeInputButtonClick()\"\n class=\"aspect-square h-full w-auto\"\n [attr.aria-label]=\"messages().openTimePicker\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"popupId\"\n [disabled]=\"disabled() || readonly()\"\n [tabindex]=\"-1\">\n <svg lucideClock [size]=\"16\" aria-hidden=\"true\"></svg>\n </button>\n </ng-template>\n</mona-text-box>\n\n<ng-template #timePopupTemplate>\n <div\n [class]=\"pickerPopupClass()\"\n [id]=\"popupId\"\n aria-modal=\"true\"\n role=\"dialog\"\n [attr.aria-label]=\"messages().timePicker\"\n cdkTrapFocus>\n <mona-time-selector\n [value]=\"value()\"\n (valueChange)=\"onTimeSelectorValueChange($event)\"\n [max]=\"max()\"\n [min]=\"min()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [hourFormat]=\"hourFormat()\"\n [hourStep]=\"hourStep()\"\n [minuteStep]=\"minuteStep()\"\n [secondStep]=\"secondStep()\"\n [size]=\"size()\"\n [rounded]=\"rounded()\"\n [showSeconds]=\"showSeconds()\"></mona-time-selector>\n </div>\n</ng-template>\n" }]
478
480
  }], ctorParameters: () => [], propDecorators: { timePopupTemplateRef: [{ type: i0.ViewChild, args: ["timePopupTemplate", { isSignal: true }] }], close: [{ type: i0.Output, args: ["close"] }], closed: [{ type: i0.Output, args: ["closed"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], hourFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourFormat", required: false }] }], hourStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourStep", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], open: [{ type: i0.Output, args: ["open"] }], opened: [{ type: i0.Output, args: ["opened"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], popupClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "popupClass", required: false }] }], popupHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "popupHeight", required: false }] }], popupWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "popupWidth", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], readonlyInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonlyInput", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], secondStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondStep", required: false }] }], showClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
479
481
 
480
482
  /*
@@ -24,7 +24,7 @@ const timeSelectorBaseThemeVariants = cva(`
24
24
  size: {
25
25
  small: "text-xs",
26
26
  medium: "text-sm",
27
- large: "text-md"
27
+ large: "text-base"
28
28
  }
29
29
  }
30
30
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanahoshi/mona-ui",
3
- "version": "0.24.2",
3
+ "version": "0.24.4",
4
4
  "description": "A very light Angular UI framework",
5
5
  "author": "Nanahoshi",
6
6
  "repository": {
@@ -144,6 +144,12 @@ declare class DropdownSuffixTemplateDirective {
144
144
  declare class DropdownService {
145
145
  readonly beforeKeydown$: Subject<PreventableEvent<KeyboardEvent>>;
146
146
  readonly beforeNavigate$: Subject<PreventableEvent<KeyboardEvent>>;
147
+ /**
148
+ * Set when the popup finishes closing: whether focus may return to the dropdown. It is `false` when the user
149
+ * moved focus outside the dropdown and its popup first, for example by clicking another field or pressing
150
+ * Tab. Read it from `popupCloseComplete$` before calling `focus()`.
151
+ */
152
+ readonly focusCanReturn: i0.WritableSignal<boolean>;
147
153
  readonly keydown$: Subject<KeyboardEvent>;
148
154
  readonly popupCloseComplete$: Subject<PopupCloseEvent<unknown>>;
149
155
  readonly popupCloseStart$: Subject<PopupCloseEvent<unknown>>;
@@ -80,6 +80,14 @@ type Action<P = void, R = void> = (arg: P) => R;
80
80
  */
81
81
  declare const createElementControlId: (level?: number, length?: number) => string;
82
82
 
83
+ /**
84
+ * Whether a closing popup may move focus back to its trigger without taking it from somewhere the user put it.
85
+ * That holds while focus is on the page itself or inside one of `containers`, usually the trigger and the popup.
86
+ * A click on another field that closed the popup, or a Tab out of it, must leave focus where it went.
87
+ * This is the rule the CDK dialog uses.
88
+ */
89
+ declare function focusCanReturn(document: Document, ...containers: ReadonlyArray<Element | null | undefined>): boolean;
90
+
83
91
  declare function focusElement(host: HTMLElement, element: HTMLElement | ElementRef<HTMLElement> | string): void;
84
92
 
85
93
  declare const rxFromResize: (target: Element | Iterable<Element>, options?: ResizeObserverOptions) => Observable<ResizeObserverEntry[]>;
@@ -153,5 +161,5 @@ interface TypeaheadModifiers {
153
161
  declare const isTypeaheadKey: (key: string, modifiers?: TypeaheadModifiers) => boolean;
154
162
  declare const setupTypeahead: (typeaheadKey$: Observable<string>) => Observable<string>;
155
163
 
156
- export { AttributeBinderDirective, NavigationKeys, NavigationKeysList, createElementControlId, focusElement, hex2rgba, hsla2hsva, hsva2rgba, isNavigationKey, isTypeaheadKey, isValidHex, isValidHsla, isValidRgb, restoreOverlayScroll, rgba2hex, rgba2hsla, rgba2hsva, rxFromResize, rxTimeout, setWindowStyles, setupTypeahead, string2Hsla, string2rgba, themeControlBackdropClasses, themeControlSurfaceClasses, themeOverlayBackdropClasses, themeOverlaySurfaceClasses, themeRaisedBackdropClasses, themeRaisedSurfaceClasses, toCssValue };
164
+ export { AttributeBinderDirective, NavigationKeys, NavigationKeysList, createElementControlId, focusCanReturn, focusElement, hex2rgba, hsla2hsva, hsva2rgba, isNavigationKey, isTypeaheadKey, isValidHex, isValidHsla, isValidRgb, restoreOverlayScroll, rgba2hex, rgba2hsla, rgba2hsva, rxFromResize, rxTimeout, setWindowStyles, setupTypeahead, string2Hsla, string2rgba, themeControlBackdropClasses, themeControlSurfaceClasses, themeOverlayBackdropClasses, themeOverlaySurfaceClasses, themeRaisedBackdropClasses, themeRaisedSurfaceClasses, toCssValue };
157
165
  export type { Action, AttributeConfig, Channel, ColorScheme, HSL, HSLA, HSV, HSVA, HSVChannel, RGB, RGBA, RGBAChannel, RGBChannel, TypeaheadModifiers, VariantInputs, WindowStyleSettings };