@vadem/ui 0.0.0-stage → 0.0.1
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.
- package/LICENSE-lucide.txt +43 -0
- package/README.md +19 -2
- package/fesm2022/vadem-ui-accordion.mjs +139 -0
- package/fesm2022/vadem-ui-accordion.mjs.map +1 -0
- package/fesm2022/vadem-ui-active-filters.mjs +355 -0
- package/fesm2022/vadem-ui-active-filters.mjs.map +1 -0
- package/fesm2022/vadem-ui-alert-dialog.mjs +433 -0
- package/fesm2022/vadem-ui-alert-dialog.mjs.map +1 -0
- package/fesm2022/vadem-ui-alert.mjs +266 -0
- package/fesm2022/vadem-ui-alert.mjs.map +1 -0
- package/fesm2022/vadem-ui-autocomplete.mjs +715 -0
- package/fesm2022/vadem-ui-autocomplete.mjs.map +1 -0
- package/fesm2022/vadem-ui-avatar.mjs +219 -0
- package/fesm2022/vadem-ui-avatar.mjs.map +1 -0
- package/fesm2022/vadem-ui-badge.mjs +86 -0
- package/fesm2022/vadem-ui-badge.mjs.map +1 -0
- package/fesm2022/vadem-ui-breadcrumbs.mjs +61 -0
- package/fesm2022/vadem-ui-breadcrumbs.mjs.map +1 -0
- package/fesm2022/vadem-ui-button.mjs +220 -0
- package/fesm2022/vadem-ui-button.mjs.map +1 -0
- package/fesm2022/vadem-ui-calendar.mjs +1314 -0
- package/fesm2022/vadem-ui-calendar.mjs.map +1 -0
- package/fesm2022/vadem-ui-card.mjs +271 -0
- package/fesm2022/vadem-ui-card.mjs.map +1 -0
- package/fesm2022/vadem-ui-character-count.mjs +269 -0
- package/fesm2022/vadem-ui-character-count.mjs.map +1 -0
- package/fesm2022/vadem-ui-chart.mjs +1984 -0
- package/fesm2022/vadem-ui-chart.mjs.map +1 -0
- package/fesm2022/vadem-ui-checkbox.mjs +67 -0
- package/fesm2022/vadem-ui-checkbox.mjs.map +1 -0
- package/fesm2022/vadem-ui-color-picker.mjs +815 -0
- package/fesm2022/vadem-ui-color-picker.mjs.map +1 -0
- package/fesm2022/vadem-ui-command.mjs +735 -0
- package/fesm2022/vadem-ui-command.mjs.map +1 -0
- package/fesm2022/vadem-ui-copy-button.mjs +375 -0
- package/fesm2022/vadem-ui-copy-button.mjs.map +1 -0
- package/fesm2022/vadem-ui-date-range-picker.mjs +1011 -0
- package/fesm2022/vadem-ui-date-range-picker.mjs.map +1 -0
- package/fesm2022/vadem-ui-description-list.mjs +59 -0
- package/fesm2022/vadem-ui-description-list.mjs.map +1 -0
- package/fesm2022/vadem-ui-dialog.mjs +587 -0
- package/fesm2022/vadem-ui-dialog.mjs.map +1 -0
- package/fesm2022/vadem-ui-dropzone.mjs +176 -0
- package/fesm2022/vadem-ui-dropzone.mjs.map +1 -0
- package/fesm2022/vadem-ui-editable-text.mjs +641 -0
- package/fesm2022/vadem-ui-editable-text.mjs.map +1 -0
- package/fesm2022/vadem-ui-empty-state.mjs +175 -0
- package/fesm2022/vadem-ui-empty-state.mjs.map +1 -0
- package/fesm2022/vadem-ui-expandable-text.mjs +244 -0
- package/fesm2022/vadem-ui-expandable-text.mjs.map +1 -0
- package/fesm2022/vadem-ui-field.mjs +583 -0
- package/fesm2022/vadem-ui-field.mjs.map +1 -0
- package/fesm2022/vadem-ui-file-preview.mjs +532 -0
- package/fesm2022/vadem-ui-file-preview.mjs.map +1 -0
- package/fesm2022/vadem-ui-file-size.mjs +83 -0
- package/fesm2022/vadem-ui-file-size.mjs.map +1 -0
- package/fesm2022/vadem-ui-form-error-summary.mjs +201 -0
- package/fesm2022/vadem-ui-form-error-summary.mjs.map +1 -0
- package/fesm2022/vadem-ui-icon.mjs +303 -0
- package/fesm2022/vadem-ui-icon.mjs.map +1 -0
- package/fesm2022/vadem-ui-input.mjs +120 -0
- package/fesm2022/vadem-ui-input.mjs.map +1 -0
- package/fesm2022/vadem-ui-kbd.mjs +286 -0
- package/fesm2022/vadem-ui-kbd.mjs.map +1 -0
- package/fesm2022/vadem-ui-keyboard.mjs +105 -0
- package/fesm2022/vadem-ui-keyboard.mjs.map +1 -0
- package/fesm2022/vadem-ui-locale.mjs +443 -0
- package/fesm2022/vadem-ui-locale.mjs.map +1 -0
- package/fesm2022/vadem-ui-menu.mjs +637 -0
- package/fesm2022/vadem-ui-menu.mjs.map +1 -0
- package/fesm2022/vadem-ui-navigation-list.mjs +130 -0
- package/fesm2022/vadem-ui-navigation-list.mjs.map +1 -0
- package/fesm2022/vadem-ui-number-input.mjs +264 -0
- package/fesm2022/vadem-ui-number-input.mjs.map +1 -0
- package/fesm2022/vadem-ui-otp-input.mjs +86 -0
- package/fesm2022/vadem-ui-otp-input.mjs.map +1 -0
- package/fesm2022/vadem-ui-pagination.mjs +353 -0
- package/fesm2022/vadem-ui-pagination.mjs.map +1 -0
- package/fesm2022/vadem-ui-password-input.mjs +213 -0
- package/fesm2022/vadem-ui-password-input.mjs.map +1 -0
- package/fesm2022/vadem-ui-popover.mjs +178 -0
- package/fesm2022/vadem-ui-popover.mjs.map +1 -0
- package/fesm2022/vadem-ui-progress.mjs +331 -0
- package/fesm2022/vadem-ui-progress.mjs.map +1 -0
- package/fesm2022/vadem-ui-radio.mjs +39 -0
- package/fesm2022/vadem-ui-radio.mjs.map +1 -0
- package/fesm2022/vadem-ui-range-slider.mjs +561 -0
- package/fesm2022/vadem-ui-range-slider.mjs.map +1 -0
- package/fesm2022/vadem-ui-rating.mjs +404 -0
- package/fesm2022/vadem-ui-rating.mjs.map +1 -0
- package/fesm2022/vadem-ui-relative-time.mjs +217 -0
- package/fesm2022/vadem-ui-relative-time.mjs.map +1 -0
- package/fesm2022/vadem-ui-reorder-list.mjs +601 -0
- package/fesm2022/vadem-ui-reorder-list.mjs.map +1 -0
- package/fesm2022/vadem-ui-scroll-area.mjs +65 -0
- package/fesm2022/vadem-ui-scroll-area.mjs.map +1 -0
- package/fesm2022/vadem-ui-search-input.mjs +307 -0
- package/fesm2022/vadem-ui-search-input.mjs.map +1 -0
- package/fesm2022/vadem-ui-segmented.mjs +297 -0
- package/fesm2022/vadem-ui-segmented.mjs.map +1 -0
- package/fesm2022/vadem-ui-select.mjs +1462 -0
- package/fesm2022/vadem-ui-select.mjs.map +1 -0
- package/fesm2022/vadem-ui-sidebar.mjs +493 -0
- package/fesm2022/vadem-ui-sidebar.mjs.map +1 -0
- package/fesm2022/vadem-ui-skeleton.mjs +106 -0
- package/fesm2022/vadem-ui-skeleton.mjs.map +1 -0
- package/fesm2022/vadem-ui-slider.mjs +60 -0
- package/fesm2022/vadem-ui-slider.mjs.map +1 -0
- package/fesm2022/vadem-ui-splitter.mjs +438 -0
- package/fesm2022/vadem-ui-splitter.mjs.map +1 -0
- package/fesm2022/vadem-ui-stat.mjs +369 -0
- package/fesm2022/vadem-ui-stat.mjs.map +1 -0
- package/fesm2022/vadem-ui-stepper.mjs +483 -0
- package/fesm2022/vadem-ui-stepper.mjs.map +1 -0
- package/fesm2022/vadem-ui-switch.mjs +45 -0
- package/fesm2022/vadem-ui-switch.mjs.map +1 -0
- package/fesm2022/vadem-ui-table.mjs +593 -0
- package/fesm2022/vadem-ui-table.mjs.map +1 -0
- package/fesm2022/vadem-ui-tabs.mjs +405 -0
- package/fesm2022/vadem-ui-tabs.mjs.map +1 -0
- package/fesm2022/vadem-ui-tag-input.mjs +685 -0
- package/fesm2022/vadem-ui-tag-input.mjs.map +1 -0
- package/fesm2022/vadem-ui-text-highlight.mjs +126 -0
- package/fesm2022/vadem-ui-text-highlight.mjs.map +1 -0
- package/fesm2022/vadem-ui-time-input.mjs +430 -0
- package/fesm2022/vadem-ui-time-input.mjs.map +1 -0
- package/fesm2022/vadem-ui-timeline.mjs +313 -0
- package/fesm2022/vadem-ui-timeline.mjs.map +1 -0
- package/fesm2022/vadem-ui-toast.mjs +397 -0
- package/fesm2022/vadem-ui-toast.mjs.map +1 -0
- package/fesm2022/vadem-ui-toolbar.mjs +396 -0
- package/fesm2022/vadem-ui-toolbar.mjs.map +1 -0
- package/fesm2022/vadem-ui-tooltip.mjs +218 -0
- package/fesm2022/vadem-ui-tooltip.mjs.map +1 -0
- package/fesm2022/vadem-ui-tree.mjs +551 -0
- package/fesm2022/vadem-ui-tree.mjs.map +1 -0
- package/fesm2022/vadem-ui-upload-list.mjs +408 -0
- package/fesm2022/vadem-ui-upload-list.mjs.map +1 -0
- package/fesm2022/vadem-ui.mjs +4 -0
- package/fesm2022/vadem-ui.mjs.map +1 -0
- package/fonts/jetbrains-mono/OFL.txt +93 -0
- package/fonts/jetbrains-mono/jetbrains-mono-cyrillic.woff2 +0 -0
- package/fonts/jetbrains-mono/jetbrains-mono-latin.woff2 +0 -0
- package/fonts/manrope/OFL.txt +93 -0
- package/fonts/manrope/manrope-cyrillic.woff2 +0 -0
- package/fonts/manrope/manrope-latin.woff2 +0 -0
- package/package.json +306 -3
- package/styles/base.css +160 -0
- package/styles/components/accordion.css +130 -0
- package/styles/components/active-filters.css +42 -0
- package/styles/components/alert-dialog.css +34 -0
- package/styles/components/alert.css +159 -0
- package/styles/components/autocomplete.css +97 -0
- package/styles/components/avatar.css +90 -0
- package/styles/components/badge.css +91 -0
- package/styles/components/breadcrumbs.css +94 -0
- package/styles/components/button.css +174 -0
- package/styles/components/calendar.css +186 -0
- package/styles/components/card.css +206 -0
- package/styles/components/character-count.css +41 -0
- package/styles/components/chart.css +308 -0
- package/styles/components/checkbox.css +115 -0
- package/styles/components/color-picker.css +158 -0
- package/styles/components/command.css +175 -0
- package/styles/components/copy-button.css +58 -0
- package/styles/components/date-range-picker.css +153 -0
- package/styles/components/description-list.css +69 -0
- package/styles/components/dialog.css +334 -0
- package/styles/components/dropzone.css +89 -0
- package/styles/components/editable-text.css +58 -0
- package/styles/components/empty-state.css +72 -0
- package/styles/components/expandable-text.css +24 -0
- package/styles/components/field.css +148 -0
- package/styles/components/file-preview.css +65 -0
- package/styles/components/form-error-summary.css +71 -0
- package/styles/components/icon.css +38 -0
- package/styles/components/index.css +80 -0
- package/styles/components/input.css +171 -0
- package/styles/components/kbd.css +42 -0
- package/styles/components/menu.css +168 -0
- package/styles/components/multi-select.css +156 -0
- package/styles/components/navigation-list.css +147 -0
- package/styles/components/number-input.css +33 -0
- package/styles/components/otp-input.css +26 -0
- package/styles/components/pagination.css +127 -0
- package/styles/components/password-input.css +20 -0
- package/styles/components/popover.css +93 -0
- package/styles/components/progress-ring.css +112 -0
- package/styles/components/progress.css +162 -0
- package/styles/components/radio.css +117 -0
- package/styles/components/range-slider.css +158 -0
- package/styles/components/rating.css +100 -0
- package/styles/components/reorder-list.css +143 -0
- package/styles/components/scroll-area.css +39 -0
- package/styles/components/search-input.css +25 -0
- package/styles/components/segmented.css +136 -0
- package/styles/components/select.css +257 -0
- package/styles/components/sidebar.css +105 -0
- package/styles/components/skeleton.css +83 -0
- package/styles/components/slider.css +132 -0
- package/styles/components/splitter.css +83 -0
- package/styles/components/stat.css +62 -0
- package/styles/components/stepper.css +236 -0
- package/styles/components/switch.css +107 -0
- package/styles/components/table.css +195 -0
- package/styles/components/tabs.css +198 -0
- package/styles/components/tag-input.css +138 -0
- package/styles/components/text-highlight.css +22 -0
- package/styles/components/time-input.css +96 -0
- package/styles/components/timeline.css +182 -0
- package/styles/components/toast.css +146 -0
- package/styles/components/toolbar.css +72 -0
- package/styles/components/tooltip.css +93 -0
- package/styles/components/tree.css +88 -0
- package/styles/components/upload-list.css +113 -0
- package/styles/fonts.css +67 -0
- package/styles/index.css +14 -0
- package/styles/reset.css +57 -0
- package/styles/tokens/primitives.css +364 -0
- package/styles/tokens/semantic.css +233 -0
- package/types/vadem-ui-accordion.d.ts +43 -0
- package/types/vadem-ui-active-filters.d.ts +73 -0
- package/types/vadem-ui-alert-dialog.d.ts +87 -0
- package/types/vadem-ui-alert.d.ts +64 -0
- package/types/vadem-ui-autocomplete.d.ts +156 -0
- package/types/vadem-ui-avatar.d.ts +59 -0
- package/types/vadem-ui-badge.d.ts +25 -0
- package/types/vadem-ui-breadcrumbs.d.ts +22 -0
- package/types/vadem-ui-button.d.ts +52 -0
- package/types/vadem-ui-calendar.d.ts +327 -0
- package/types/vadem-ui-card.d.ts +58 -0
- package/types/vadem-ui-character-count.d.ts +71 -0
- package/types/vadem-ui-chart.d.ts +452 -0
- package/types/vadem-ui-checkbox.d.ts +20 -0
- package/types/vadem-ui-color-picker.d.ts +171 -0
- package/types/vadem-ui-command.d.ts +145 -0
- package/types/vadem-ui-copy-button.d.ts +90 -0
- package/types/vadem-ui-date-range-picker.d.ts +216 -0
- package/types/vadem-ui-description-list.d.ts +15 -0
- package/types/vadem-ui-dialog.d.ts +174 -0
- package/types/vadem-ui-dropzone.d.ts +73 -0
- package/types/vadem-ui-editable-text.d.ts +141 -0
- package/types/vadem-ui-empty-state.d.ts +34 -0
- package/types/vadem-ui-expandable-text.d.ts +52 -0
- package/types/vadem-ui-field.d.ts +180 -0
- package/types/vadem-ui-file-preview.d.ts +108 -0
- package/types/vadem-ui-file-size.d.ts +18 -0
- package/types/vadem-ui-form-error-summary.d.ts +46 -0
- package/types/vadem-ui-icon.d.ts +152 -0
- package/types/vadem-ui-input.d.ts +31 -0
- package/types/vadem-ui-kbd.d.ts +66 -0
- package/types/vadem-ui-keyboard.d.ts +61 -0
- package/types/vadem-ui-locale.d.ts +241 -0
- package/types/vadem-ui-menu.d.ts +132 -0
- package/types/vadem-ui-navigation-list.d.ts +28 -0
- package/types/vadem-ui-number-input.d.ts +39 -0
- package/types/vadem-ui-otp-input.d.ts +15 -0
- package/types/vadem-ui-pagination.d.ts +96 -0
- package/types/vadem-ui-password-input.d.ts +36 -0
- package/types/vadem-ui-popover.d.ts +48 -0
- package/types/vadem-ui-progress.d.ts +95 -0
- package/types/vadem-ui-radio.d.ts +11 -0
- package/types/vadem-ui-range-slider.d.ts +136 -0
- package/types/vadem-ui-rating.d.ts +95 -0
- package/types/vadem-ui-relative-time.d.ts +58 -0
- package/types/vadem-ui-reorder-list.d.ts +120 -0
- package/types/vadem-ui-scroll-area.d.ts +13 -0
- package/types/vadem-ui-search-input.d.ts +58 -0
- package/types/vadem-ui-segmented.d.ts +76 -0
- package/types/vadem-ui-select.d.ts +303 -0
- package/types/vadem-ui-sidebar.d.ts +70 -0
- package/types/vadem-ui-skeleton.d.ts +28 -0
- package/types/vadem-ui-slider.d.ts +17 -0
- package/types/vadem-ui-splitter.d.ts +99 -0
- package/types/vadem-ui-stat.d.ts +101 -0
- package/types/vadem-ui-stepper.d.ts +108 -0
- package/types/vadem-ui-switch.d.ts +10 -0
- package/types/vadem-ui-table.d.ts +172 -0
- package/types/vadem-ui-tabs.d.ts +101 -0
- package/types/vadem-ui-tag-input.d.ts +170 -0
- package/types/vadem-ui-text-highlight.d.ts +27 -0
- package/types/vadem-ui-time-input.d.ts +47 -0
- package/types/vadem-ui-timeline.d.ts +54 -0
- package/types/vadem-ui-toast.d.ts +104 -0
- package/types/vadem-ui-toolbar.d.ts +80 -0
- package/types/vadem-ui-tooltip.d.ts +71 -0
- package/types/vadem-ui-tree.d.ts +123 -0
- package/types/vadem-ui-upload-list.d.ts +70 -0
- package/types/vadem-ui.d.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vadem-ui-tag-input.mjs","names":[],"sources":["../../../projects/ui/tag-input/src/core/tags.ts","../../../projects/ui/tag-input/src/tag-input.ts"],"sourcesContent":["/** Canonical display/editing identity without discarding accents or internal whitespace. */\nexport function vdTagKey(tag: string): string {\n return tag.trim().normalize('NFC').toLowerCase();\n}\n\nexport function vdNormalizeTags(value: readonly string[] | null | undefined): string[] {\n const seen = new Set<string>();\n return (value ?? [])\n .filter((tag) => {\n if (typeof tag !== 'string') return false;\n const key = vdTagKey(tag);\n if (!key || seen.has(key)) return false;\n seen.add(key);\n return true;\n })\n .map((tag) => tag.trim().normalize('NFC'));\n}\n\nexport function vdAddTag(\n value: readonly string[],\n draft: string,\n):\n | { kind: 'empty'; tag: string }\n | { kind: 'duplicate'; tag: string }\n | { kind: 'added'; tag: string; value: string[] } {\n const tag = draft.trim().normalize('NFC');\n if (!tag) return { kind: 'empty', tag };\n const tags = vdNormalizeTags(value);\n if (tags.some((item) => vdTagKey(item) === vdTagKey(tag))) return { kind: 'duplicate', tag };\n return { kind: 'added', tag, value: [...tags, tag] };\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n Injector,\n OnInit,\n Signal,\n ViewEncapsulation,\n afterRenderEffect,\n booleanAttribute,\n computed,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n viewChild,\n viewChildren,\n} from '@angular/core';\nimport { NgControl } from '@angular/forms';\nimport { FormField, FormValueControl } from '@angular/forms/signals';\nimport {\n VD_FIELD,\n VdFieldControl,\n VdNgControlState,\n vdNgControlState,\n vdUniqueId,\n} from '@vadem/ui/field';\nimport { VdIcon } from '@vadem/ui/icon';\nimport { vdAddTag, vdNormalizeTags, vdTagKey } from './core/tags';\nimport { vdTexts } from '@vadem/ui/locale';\n\nexport type VdTagInputSize = 'sm' | 'md' | 'lg';\n\n/** User-defined tags with explicit editing actions. Spec: specs/tag-input.md. */\n@Component({\n selector: 'vd-tag-input',\n imports: [VdIcon],\n template: `\n <div class=\"vd-input-group vd-tag-input__field\" (pointerdown)=\"onFieldPointerDown($event)\">\n @if (tags().length) {\n <ul class=\"vd-tag-input__tags\" role=\"list\" [attr.aria-label]=\"t().tagsLabel\">\n @for (tag of tags(); track tagKey(tag)) {\n <li class=\"vd-tag-input__tag\">\n <span class=\"vd-tag-input__label\">{{ tag }}</span>\n <button\n #removeButton\n class=\"vd-tag-input__remove\"\n type=\"button\"\n [disabled]=\"locked()\"\n [attr.aria-label]=\"t().removeLabel(tag)\"\n (click)=\"remove(tag)\"\n (keydown.escape)=\"returnToInput($event)\"\n >\n <vd-icon name=\"x\" />\n </button>\n </li>\n }\n </ul>\n }\n <input\n #draftInput\n type=\"text\"\n class=\"vd-input vd-tag-input__input\"\n autocomplete=\"off\"\n [id]=\"controlId\"\n [attr.data-size]=\"size()\"\n [value]=\"draft()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [readOnly]=\"readonly()\"\n [attr.data-invalid]=\"showInvalid() ? '' : null\"\n [attr.aria-invalid]=\"showInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [attr.aria-describedby]=\"descriptionIds()\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-labelledby]=\"ariaLabelledby() ?? null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (compositionstart)=\"composing = true\"\n (compositionend)=\"composing = false\"\n />\n <button\n class=\"vd-button vd-tag-input__add\"\n type=\"button\"\n data-variant=\"ghost\"\n [attr.data-size]=\"size()\"\n [disabled]=\"locked() || !draft().trim()\"\n (click)=\"add()\"\n >\n {{ t().addLabel }}\n </button>\n </div>\n <p\n class=\"vd-tag-input__feedback\"\n [id]=\"feedbackId\"\n role=\"status\"\n aria-atomic=\"true\"\n [textContent]=\"feedback()\"\n ></p>\n `,\n host: {\n class: 'vd-tag-input',\n '[attr.data-size]': 'size()',\n '[attr.data-invalid]': 'showInvalid() ? \"\" : null',\n '[attr.data-disabled]': 'isDisabled() ? \"\" : null',\n '[attr.data-readonly]': 'readonly() ? \"\" : null',\n '[attr.aria-label]': 'null',\n '[attr.aria-labelledby]': 'null',\n '(focusout)': 'onFocusOut($event)',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n})\nexport class VdTagInput implements FormValueControl<string[]>, OnInit {\n readonly value = model<string[]>([]);\n readonly size = input<VdTagInputSize>('md');\n readonly disabled = input(false, { transform: booleanAttribute });\n readonly readonly = input(false, { transform: booleanAttribute });\n readonly invalid = input(false, { transform: booleanAttribute });\n readonly touched = input(false, { transform: booleanAttribute });\n readonly required = input(false, { transform: booleanAttribute });\n readonly placeholder = input('');\n readonly ariaLabel = input<string | undefined>(undefined, { alias: 'aria-label' });\n readonly ariaLabelledby = input<string | undefined>(undefined, { alias: 'aria-labelledby' });\n readonly addLabel = input<string>();\n readonly tagsLabel = input<string>();\n readonly removeLabel = input<(tag: string) => string>();\n readonly addedMessage = input<(tag: string) => string>();\n readonly removedMessage = input<(tag: string) => string>();\n readonly duplicateMessage = input<(tag: string) => string>();\n /** Text inputs resolved against the active VD_LOCALE. */\n protected readonly t = vdTexts('tagInput', {\n addLabel: this.addLabel,\n tagsLabel: this.tagsLabel,\n removeLabel: this.removeLabel,\n addedMessage: this.addedMessage,\n removedMessage: this.removedMessage,\n duplicateMessage: this.duplicateMessage,\n });\n readonly touch = output<void>();\n\n readonly controlId = vdUniqueId('vd-tags');\n protected readonly feedbackId = vdUniqueId('vd-tags-feedback');\n protected readonly tagKey = vdTagKey;\n protected readonly tags = computed(() => vdNormalizeTags(this.value()));\n protected readonly draft = signal('');\n protected readonly feedback = signal('');\n protected composing = false;\n private readonly pendingFeedback = signal<string | null>(null);\n private readonly inputRef = viewChild<ElementRef<HTMLInputElement>>('draftInput');\n private readonly removeButtons = viewChildren<ElementRef<HTMLButtonElement>>('removeButton');\n private readonly hostEl: HTMLElement = inject(ElementRef).nativeElement;\n private readonly field = inject(VD_FIELD, { optional: true });\n private readonly injector = inject(Injector);\n private readonly destroyRef = inject(DestroyRef);\n private readonly formBound = signal(false);\n private readonly ngSource = signal<Signal<VdNgControlState> | null>(null);\n private readonly ngState = computed(\n () =>\n this.ngSource()?.() ?? { invalid: false, touched: false, disabled: false, required: false },\n );\n protected readonly isDisabled = computed(() => this.disabled() || this.ngState().disabled);\n protected readonly locked = computed(() => this.isDisabled() || this.readonly());\n protected readonly showInvalid = computed(\n () =>\n (this.invalid() && (!this.formBound() || this.touched())) ||\n (this.ngState().invalid && this.ngState().touched),\n );\n protected readonly isRequired = computed(() => this.required() || this.ngState().required);\n protected readonly descriptionIds = computed(\n () =>\n [this.field?.describedBy(), this.feedback() ? this.feedbackId : null]\n .filter(Boolean)\n .join(' ') || null,\n );\n private lastValue: string[] | null = null;\n\n constructor() {\n const control: VdFieldControl = {\n id: signal(this.controlId),\n invalid: this.showInvalid,\n required: this.isRequired,\n disabled: this.isDisabled,\n };\n this.field?.register(control);\n this.destroyRef.onDestroy(() => this.field?.unregister(control));\n // All form APIs bind the model directly. External replacements clear pending editing.\n afterRenderEffect(() => {\n const value = this.value();\n if (value !== this.lastValue) {\n this.lastValue = value;\n untracked(() => this.reset());\n }\n });\n // An empty render separates repeated identical status messages; no timers or SSR DOM access.\n afterRenderEffect(() => {\n const message = this.pendingFeedback();\n if (message !== null) {\n this.pendingFeedback.set(null);\n this.feedback.set(message);\n }\n });\n }\n\n ngOnInit(): void {\n const ng = this.injector.get(NgControl, null, { self: true, optional: true });\n this.formBound.set(\n !!ng || !!this.injector.get(FormField, null, { self: true, optional: true }),\n );\n this.ngSource.set(vdNgControlState(ng, this.destroyRef));\n }\n\n focus(options?: FocusOptions): void {\n this.inputRef()?.nativeElement.focus(options);\n }\n reset(): void {\n this.draft.set('');\n // Reset the native draft even if input and reset happened before a binding render.\n const input = this.inputRef()?.nativeElement;\n if (input) input.value = '';\n this.feedback.set('');\n this.pendingFeedback.set(null);\n this.composing = false;\n }\n protected onInput(event: Event): void {\n this.draft.set((event.target as HTMLInputElement).value);\n this.feedback.set('');\n this.pendingFeedback.set(null);\n }\n protected add(): void {\n if (this.locked() || this.composing) return;\n const result = vdAddTag(this.tags(), this.draft());\n if (result.kind === 'empty') return;\n if (result.kind === 'duplicate') this.announce(this.t().duplicateMessage(result.tag));\n else {\n this.commit(result.value);\n this.draft.set('');\n this.announce(this.t().addedMessage(result.tag));\n }\n this.focus();\n }\n protected remove(tag: string): void {\n if (this.locked() || !this.tags().includes(tag)) return;\n this.commit(this.tags().filter((item) => item !== tag));\n this.focus();\n this.announce(this.t().removedMessage(tag));\n }\n protected returnToInput(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.focus();\n }\n protected onKeydown(event: KeyboardEvent): void {\n if (\n event.isComposing ||\n this.composing ||\n this.locked() ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n )\n return;\n if (event.key === 'Enter') {\n event.preventDefault();\n this.add();\n } else if (event.key === 'Backspace' && this.draft() === '') {\n const last = this.removeButtons().at(-1);\n if (last) {\n event.preventDefault();\n last.nativeElement.focus();\n }\n }\n }\n protected onFocusOut(event: FocusEvent): void {\n if (event.relatedTarget && this.hostEl.contains(event.relatedTarget as Node)) return;\n this.touch.emit();\n }\n protected onFieldPointerDown(event: PointerEvent): void {\n if (!this.isDisabled() && event.target === event.currentTarget) {\n event.preventDefault();\n this.focus();\n }\n }\n private commit(value: string[]): void {\n this.lastValue = value;\n this.value.set(value);\n }\n private announce(message: string): void {\n this.feedback.set('');\n this.pendingFeedback.set(message);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { ChangeDetectionStrategy, Component, ViewEncapsulation, computed, input } from "@angular/core";
|
|
3
|
+
function vdTextHighlightParts(text, query) {
|
|
4
|
+
if (!text) return [];
|
|
5
|
+
if (!query) return [{
|
|
6
|
+
text,
|
|
7
|
+
matched: false
|
|
8
|
+
}];
|
|
9
|
+
const pattern = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "giu");
|
|
10
|
+
const parts = [];
|
|
11
|
+
let cursor = 0;
|
|
12
|
+
for (const match of text.matchAll(pattern)) {
|
|
13
|
+
if (match.index > cursor) parts.push({
|
|
14
|
+
text: text.slice(cursor, match.index),
|
|
15
|
+
matched: false
|
|
16
|
+
});
|
|
17
|
+
parts.push({
|
|
18
|
+
text: match[0],
|
|
19
|
+
matched: true
|
|
20
|
+
});
|
|
21
|
+
cursor = match.index + match[0].length;
|
|
22
|
+
}
|
|
23
|
+
if (cursor < text.length) parts.push({
|
|
24
|
+
text: text.slice(cursor),
|
|
25
|
+
matched: false
|
|
26
|
+
});
|
|
27
|
+
return parts;
|
|
28
|
+
}
|
|
29
|
+
var VdTextHighlight = class VdTextHighlight {
|
|
30
|
+
text = input.required(...ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []);
|
|
31
|
+
query = input("", ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
|
|
32
|
+
parts = computed(() => vdTextHighlightParts(this.text(), this.query()), ...ngDevMode ? [{ debugName: "parts" }] : /* istanbul ignore next */ []);
|
|
33
|
+
matched = computed(() => this.parts().some((part) => part.matched), ...ngDevMode ? [{ debugName: "matched" }] : /* istanbul ignore next */ []);
|
|
34
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
35
|
+
minVersion: "12.0.0",
|
|
36
|
+
version: "22.2.1",
|
|
37
|
+
ngImport: i0,
|
|
38
|
+
type: VdTextHighlight,
|
|
39
|
+
deps: [],
|
|
40
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
41
|
+
});
|
|
42
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
43
|
+
minVersion: "17.0.0",
|
|
44
|
+
version: "22.2.1",
|
|
45
|
+
type: VdTextHighlight,
|
|
46
|
+
isStandalone: true,
|
|
47
|
+
selector: "vd-text-highlight",
|
|
48
|
+
inputs: {
|
|
49
|
+
text: {
|
|
50
|
+
classPropertyName: "text",
|
|
51
|
+
publicName: "text",
|
|
52
|
+
isSignal: true,
|
|
53
|
+
isRequired: true,
|
|
54
|
+
transformFunction: null
|
|
55
|
+
},
|
|
56
|
+
query: {
|
|
57
|
+
classPropertyName: "query",
|
|
58
|
+
publicName: "query",
|
|
59
|
+
isSignal: true,
|
|
60
|
+
isRequired: false,
|
|
61
|
+
transformFunction: null
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
host: {
|
|
65
|
+
properties: { "attr.data-state": "!text() ? \"empty\" : matched() ? \"matched\" : \"plain\"" },
|
|
66
|
+
classAttribute: "vd-text-highlight"
|
|
67
|
+
},
|
|
68
|
+
ngImport: i0,
|
|
69
|
+
template: `@for (part of parts(); track $index) {
|
|
70
|
+
@if (part.matched) {
|
|
71
|
+
<mark class="vd-text-highlight__match">{{ part.text }}</mark>
|
|
72
|
+
} @else {
|
|
73
|
+
<ng-container>{{ part.text }}</ng-container>
|
|
74
|
+
}
|
|
75
|
+
}`,
|
|
76
|
+
isInline: true,
|
|
77
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush,
|
|
78
|
+
encapsulation: i0.ViewEncapsulation.None
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
82
|
+
minVersion: "12.0.0",
|
|
83
|
+
version: "22.2.1",
|
|
84
|
+
ngImport: i0,
|
|
85
|
+
type: VdTextHighlight,
|
|
86
|
+
decorators: [{
|
|
87
|
+
type: Component,
|
|
88
|
+
args: [{
|
|
89
|
+
selector: "vd-text-highlight",
|
|
90
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
91
|
+
encapsulation: ViewEncapsulation.None,
|
|
92
|
+
host: {
|
|
93
|
+
class: "vd-text-highlight",
|
|
94
|
+
"[attr.data-state]": "!text() ? \"empty\" : matched() ? \"matched\" : \"plain\""
|
|
95
|
+
},
|
|
96
|
+
template: `@for (part of parts(); track $index) {
|
|
97
|
+
@if (part.matched) {
|
|
98
|
+
<mark class="vd-text-highlight__match">{{ part.text }}</mark>
|
|
99
|
+
} @else {
|
|
100
|
+
<ng-container>{{ part.text }}</ng-container>
|
|
101
|
+
}
|
|
102
|
+
}`
|
|
103
|
+
}]
|
|
104
|
+
}],
|
|
105
|
+
propDecorators: {
|
|
106
|
+
text: [{
|
|
107
|
+
type: i0.Input,
|
|
108
|
+
args: [{
|
|
109
|
+
isSignal: true,
|
|
110
|
+
alias: "text",
|
|
111
|
+
required: true
|
|
112
|
+
}]
|
|
113
|
+
}],
|
|
114
|
+
query: [{
|
|
115
|
+
type: i0.Input,
|
|
116
|
+
args: [{
|
|
117
|
+
isSignal: true,
|
|
118
|
+
alias: "query",
|
|
119
|
+
required: false
|
|
120
|
+
}]
|
|
121
|
+
}]
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
export { VdTextHighlight };
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=vadem-ui-text-highlight.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vadem-ui-text-highlight.mjs","names":[],"sources":["../../../projects/ui/text-highlight/src/core/text-highlight.ts","../../../projects/ui/text-highlight/src/text-highlight.ts"],"sourcesContent":["export interface VdTextHighlightPart {\n readonly text: string;\n readonly matched: boolean;\n}\n\n/** Unicode simple case folding keeps match indices tied to the original source. */\nexport function vdTextHighlightParts(text: string, query: string): readonly VdTextHighlightPart[] {\n if (!text) return [];\n if (!query) return [{ text, matched: false }];\n const pattern = new RegExp(query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'giu');\n const parts: VdTextHighlightPart[] = [];\n let cursor = 0;\n for (const match of text.matchAll(pattern)) {\n if (match.index > cursor) parts.push({ text: text.slice(cursor, match.index), matched: false });\n parts.push({ text: match[0], matched: true });\n cursor = match.index + match[0].length;\n }\n if (cursor < text.length) parts.push({ text: text.slice(cursor), matched: false });\n return parts;\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n computed,\n input,\n} from '@angular/core';\nimport { vdTextHighlightParts } from './core/text-highlight';\n\n/** Preserves plain text while marking every literal, case-insensitive query match. */\n@Component({\n selector: 'vd-text-highlight',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'vd-text-highlight',\n '[attr.data-state]': '!text() ? \"empty\" : matched() ? \"matched\" : \"plain\"',\n },\n template: `@for (part of parts(); track $index) {\n @if (part.matched) {\n <mark class=\"vd-text-highlight__match\">{{ part.text }}</mark>\n } @else {\n <ng-container>{{ part.text }}</ng-container>\n }\n }`,\n})\nexport class VdTextHighlight {\n readonly text = input.required<string>();\n readonly query = input('');\n protected readonly parts = computed(() => vdTextHighlightParts(this.text(), this.query()));\n protected readonly matched = computed(() => this.parts().some((part) => part.matched));\n}\n"],"mappings":";;AAMA,SAAgB,qBAAqB,MAAc,OAAa;CAC9D,IAAI,CAAC,MAAM,OAAO,CAAA;CAClB,IAAI,CAAC,OAAO,OAAO,CAAC;EAAE;EAAM,SAAS;CAAK,CAAE;CAC5C,MAAM,UAAU,IAAI,OAAO,MAAM,QAAQ,uBAAuB,MAAM,GAAG,KAAK;CAC9E,MAAM,QAA+B,CAAA;CACrC,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,KAAK,SAAS,OAAO,GAAG;EAC1C,IAAI,MAAM,QAAQ,QAAQ,MAAM,KAAK;GAAE,MAAM,KAAK,MAAM,QAAQ,MAAM,KAAK;GAAG,SAAS;EAAK,CAAE;EAC9F,MAAM,KAAK;GAAE,MAAM,MAAM;GAAI,SAAS;EAAI,CAAE;EAC5C,SAAS,MAAM,QAAQ,MAAM,EAAE,CAAC;CAClC;CACA,IAAI,SAAS,KAAK,QAAQ,MAAM,KAAK;EAAE,MAAM,KAAK,MAAM,MAAM;EAAG,SAAS;CAAK,CAAE;CACjF,OAAO;AACT;ACOA,IAAa,kBAAb,MAAa,gBAAe;CACjB,OAAO,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAC/B,QAAQ,MAAM,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CACP,QAAQ,eAAe,qBAAqB,KAAK,KAAI,GAAI,KAAK,MAAK,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CACvE,UAAU,eAAe,KAAK,MAAK,CAAE,CAAC,MAAM,SAAS,KAAK,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAJ3E,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAe,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAf,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAe,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA;GAAA,YAAA,EAAA,mBAAA,4DAAA;GAAA,gBAAA;EAAA;EAAA,UAAA;EAAA,UARhB;;;;;;;EAMR,UAAA;EAAA,iBAAA,GAAA,wBAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAES,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAe,YAAA,CAAA;EAhB3B,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,iBAAiB,wBAAwB;GACzC,eAAe,kBAAkB;GACjC,MAAM;IACJ,OAAO;IACP,qBAAqB;GACtB;GACD,UAAU;;;;;;;EAOX,CAAA"}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, ElementRef, ViewContainerRef, ViewEncapsulation, afterNextRender, computed, inject, input, signal } from "@angular/core";
|
|
3
|
+
import { VdInput } from "@vadem/ui/input";
|
|
4
|
+
import { VdIcon } from "@vadem/ui/icon";
|
|
5
|
+
import { VdPopover } from "@vadem/ui/popover";
|
|
6
|
+
import { vdTexts } from "@vadem/ui/locale";
|
|
7
|
+
function timeOptions(min, max, step, initial) {
|
|
8
|
+
const seconds = (value) => {
|
|
9
|
+
if (!/^\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?$/.test(value)) return void 0;
|
|
10
|
+
const [h, m, s = 0] = value.split(":").map(Number);
|
|
11
|
+
return h < 24 && m < 60 && s < 60 ? h * 3600 + m * 60 + s : void 0;
|
|
12
|
+
};
|
|
13
|
+
const low = seconds(min), high = seconds(max), base = low ?? seconds(initial) ?? 0;
|
|
14
|
+
const increment = step === "any" ? 0 : Number(step) > 0 ? Number(step) : 60;
|
|
15
|
+
const result = [];
|
|
16
|
+
for (let minute = 0; minute < 1440; minute++) {
|
|
17
|
+
const value = minute * 60;
|
|
18
|
+
const inRange = low !== void 0 && high !== void 0 && low > high ? value >= low || value <= high : (low === void 0 || value >= low) && (high === void 0 || value <= high);
|
|
19
|
+
const quotient = increment ? (value - base) / increment : 0;
|
|
20
|
+
if (inRange && Math.abs(quotient - Math.round(quotient)) < 1e-7) result.push(`${String(Math.floor(minute / 60)).padStart(2, "0")}:${String(minute % 60).padStart(2, "0")}`);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
var VdTimePicker = class VdTimePicker {
|
|
25
|
+
label = signal("Choose time", ...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
|
|
26
|
+
emptyLabel = signal("No available times", ...ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []);
|
|
27
|
+
options = signal([], ...ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []);
|
|
28
|
+
selected = signal("", ...ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []);
|
|
29
|
+
hoursLabel = signal("Hours", ...ngDevMode ? [{ debugName: "hoursLabel" }] : /* istanbul ignore next */ []);
|
|
30
|
+
minutesLabel = signal("Minutes", ...ngDevMode ? [{ debugName: "minutesLabel" }] : /* istanbul ignore next */ []);
|
|
31
|
+
hour = signal("", ...ngDevMode ? [{ debugName: "hour" }] : /* istanbul ignore next */ []);
|
|
32
|
+
minute = signal("", ...ngDevMode ? [{ debugName: "minute" }] : /* istanbul ignore next */ []);
|
|
33
|
+
activeHour = signal(0, ...ngDevMode ? [{ debugName: "activeHour" }] : /* istanbul ignore next */ []);
|
|
34
|
+
activeMinute = signal(0, ...ngDevMode ? [{ debugName: "activeMinute" }] : /* istanbul ignore next */ []);
|
|
35
|
+
hours = computed(() => [...new Set(this.options().map((time) => time.slice(0, 2)))], ...ngDevMode ? [{ debugName: "hours" }] : /* istanbul ignore next */ []);
|
|
36
|
+
minutes = computed(() => this.options().filter((time) => time.startsWith(this.hour() + ":")).map((time) => time.slice(3)), ...ngDevMode ? [{ debugName: "minutes" }] : /* istanbul ignore next */ []);
|
|
37
|
+
changeDetector = inject(ChangeDetectorRef);
|
|
38
|
+
host = inject(ElementRef).nativeElement;
|
|
39
|
+
target;
|
|
40
|
+
onState;
|
|
41
|
+
get panel() {
|
|
42
|
+
return this.host.querySelector(".vd-time-picker");
|
|
43
|
+
}
|
|
44
|
+
open(target, label, emptyLabel, hoursLabel, minutesLabel) {
|
|
45
|
+
this.target = target;
|
|
46
|
+
this.label.set(label);
|
|
47
|
+
this.hoursLabel.set(hoursLabel);
|
|
48
|
+
this.minutesLabel.set(minutesLabel);
|
|
49
|
+
this.emptyLabel.set(emptyLabel);
|
|
50
|
+
this.options.set(timeOptions(target.min, target.max, target.step, target.getAttribute("value") ?? ""));
|
|
51
|
+
this.selected.set(target.value);
|
|
52
|
+
const initial = this.options().includes(target.value) ? target.value : this.options()[0] ?? "";
|
|
53
|
+
this.hour.set(initial.slice(0, 2));
|
|
54
|
+
this.minute.set(initial.slice(3));
|
|
55
|
+
this.activeHour.set(Math.max(0, this.hours().indexOf(this.hour())));
|
|
56
|
+
this.activeMinute.set(Math.max(0, this.minutes().indexOf(this.minute())));
|
|
57
|
+
}
|
|
58
|
+
show() {
|
|
59
|
+
if (!this.target || this.panel.matches(":popover-open")) return;
|
|
60
|
+
this.panel.showPopover({ source: this.target });
|
|
61
|
+
this.focusActive("hours");
|
|
62
|
+
}
|
|
63
|
+
toggled(event) {
|
|
64
|
+
this.onState?.(event.newState === "open");
|
|
65
|
+
}
|
|
66
|
+
leave(event) {
|
|
67
|
+
const next = event.relatedTarget;
|
|
68
|
+
if (next && next !== this.target && !this.panel.contains(next) && this.panel.matches(":popover-open")) this.panel.hidePopover();
|
|
69
|
+
}
|
|
70
|
+
choose(time) {
|
|
71
|
+
const target = this.target;
|
|
72
|
+
if (!target || target.disabled || target.readOnly || !timeOptions(target.min, target.max, target.step, target.getAttribute("value") ?? "").includes(time)) return;
|
|
73
|
+
target.value = time;
|
|
74
|
+
target.dispatchEvent(new Event("input", { bubbles: true }));
|
|
75
|
+
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
76
|
+
this.panel.hidePopover();
|
|
77
|
+
target.focus();
|
|
78
|
+
}
|
|
79
|
+
parts(column) {
|
|
80
|
+
return column === "hours" ? this.hours() : this.minutes();
|
|
81
|
+
}
|
|
82
|
+
activeIndex(column) {
|
|
83
|
+
return column === "hours" ? this.activeHour() : this.activeMinute();
|
|
84
|
+
}
|
|
85
|
+
setActive(column, index) {
|
|
86
|
+
(column === "hours" ? this.activeHour : this.activeMinute).set(index);
|
|
87
|
+
}
|
|
88
|
+
pick(part, column) {
|
|
89
|
+
if (column === "minutes") {
|
|
90
|
+
this.choose(`${this.hour()}:${part}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.hour.set(part);
|
|
94
|
+
if (!this.minutes().includes(this.minute())) this.minute.set(this.minutes()[0] ?? "");
|
|
95
|
+
this.activeMinute.set(Math.max(0, this.minutes().indexOf(this.minute())));
|
|
96
|
+
this.changeDetector.detectChanges();
|
|
97
|
+
this.focusActive("minutes");
|
|
98
|
+
}
|
|
99
|
+
navigate(event, column) {
|
|
100
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
this.pick(this.parts(column)[this.activeIndex(column)], column);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const size = this.parts(column).length;
|
|
106
|
+
let next;
|
|
107
|
+
switch (event.key) {
|
|
108
|
+
case "ArrowDown":
|
|
109
|
+
next = Math.min(size - 1, this.activeIndex(column) + 1);
|
|
110
|
+
break;
|
|
111
|
+
case "ArrowUp":
|
|
112
|
+
next = Math.max(0, this.activeIndex(column) - 1);
|
|
113
|
+
break;
|
|
114
|
+
case "Home":
|
|
115
|
+
next = 0;
|
|
116
|
+
break;
|
|
117
|
+
case "End":
|
|
118
|
+
next = size - 1;
|
|
119
|
+
break;
|
|
120
|
+
default: return;
|
|
121
|
+
}
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
this.setActive(column, next);
|
|
124
|
+
this.focusActive(column);
|
|
125
|
+
}
|
|
126
|
+
focusActive(column) {
|
|
127
|
+
const option = this.panel.querySelectorAll(`[data-column=${column}] [role=option]`)[this.activeIndex(column)];
|
|
128
|
+
if (option) {
|
|
129
|
+
option.focus({ preventScroll: true });
|
|
130
|
+
option.scrollIntoView({
|
|
131
|
+
block: "nearest",
|
|
132
|
+
behavior: "instant"
|
|
133
|
+
});
|
|
134
|
+
} else this.panel.focus();
|
|
135
|
+
}
|
|
136
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
137
|
+
minVersion: "12.0.0",
|
|
138
|
+
version: "22.2.1",
|
|
139
|
+
ngImport: i0,
|
|
140
|
+
type: VdTimePicker,
|
|
141
|
+
deps: [],
|
|
142
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
143
|
+
});
|
|
144
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
145
|
+
minVersion: "17.0.0",
|
|
146
|
+
version: "22.2.1",
|
|
147
|
+
type: VdTimePicker,
|
|
148
|
+
isStandalone: true,
|
|
149
|
+
selector: "vd-time-picker",
|
|
150
|
+
ngImport: i0,
|
|
151
|
+
template: `
|
|
152
|
+
<div
|
|
153
|
+
vdPopover
|
|
154
|
+
class="vd-time-picker"
|
|
155
|
+
[attr.aria-label]="label()"
|
|
156
|
+
(toggle)="toggled($event)"
|
|
157
|
+
(focusout)="leave($event)"
|
|
158
|
+
>
|
|
159
|
+
<div class="vd-time-picker__heading">{{ label() }}</div>
|
|
160
|
+
@if (options().length) {
|
|
161
|
+
<div class="vd-time-picker__columns">
|
|
162
|
+
@for (column of ['hours', 'minutes']; track column) {
|
|
163
|
+
<div class="vd-time-picker__column">
|
|
164
|
+
<div class="vd-time-picker__column-label">
|
|
165
|
+
{{ column === 'hours' ? hoursLabel() : minutesLabel() }}
|
|
166
|
+
</div>
|
|
167
|
+
<div
|
|
168
|
+
class="vd-time-picker__list"
|
|
169
|
+
role="listbox"
|
|
170
|
+
[attr.data-column]="column"
|
|
171
|
+
[attr.aria-label]="column === 'hours' ? hoursLabel() : minutesLabel()"
|
|
172
|
+
>
|
|
173
|
+
@for (part of parts(column); track part; let i = $index) {
|
|
174
|
+
<div
|
|
175
|
+
class="vd-time-picker__option"
|
|
176
|
+
role="option"
|
|
177
|
+
[attr.aria-selected]="part === (column === 'hours' ? hour() : minute())"
|
|
178
|
+
[attr.tabindex]="i === activeIndex(column) ? 0 : -1"
|
|
179
|
+
(keydown)="navigate($event, column)"
|
|
180
|
+
(focus)="setActive(column, i)"
|
|
181
|
+
(click)="pick(part, column)"
|
|
182
|
+
>
|
|
183
|
+
<span>{{ part }}</span
|
|
184
|
+
><vd-icon class="vd-time-picker__mark" name="check" />
|
|
185
|
+
</div>
|
|
186
|
+
}
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
}
|
|
190
|
+
</div>
|
|
191
|
+
} @else {
|
|
192
|
+
<p class="vd-time-picker__empty" role="status">{{ emptyLabel() }}</p>
|
|
193
|
+
}
|
|
194
|
+
</div>
|
|
195
|
+
`,
|
|
196
|
+
isInline: true,
|
|
197
|
+
dependencies: [{
|
|
198
|
+
kind: "directive",
|
|
199
|
+
type: VdPopover,
|
|
200
|
+
selector: "[vdPopover]",
|
|
201
|
+
inputs: ["placement"],
|
|
202
|
+
exportAs: ["vdPopover"]
|
|
203
|
+
}, {
|
|
204
|
+
kind: "component",
|
|
205
|
+
type: VdIcon,
|
|
206
|
+
selector: "vd-icon",
|
|
207
|
+
inputs: ["name", "label"]
|
|
208
|
+
}],
|
|
209
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush,
|
|
210
|
+
encapsulation: i0.ViewEncapsulation.None
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
214
|
+
minVersion: "12.0.0",
|
|
215
|
+
version: "22.2.1",
|
|
216
|
+
ngImport: i0,
|
|
217
|
+
type: VdTimePicker,
|
|
218
|
+
decorators: [{
|
|
219
|
+
type: Component,
|
|
220
|
+
args: [{
|
|
221
|
+
selector: "vd-time-picker",
|
|
222
|
+
imports: [VdPopover, VdIcon],
|
|
223
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
224
|
+
encapsulation: ViewEncapsulation.None,
|
|
225
|
+
template: `
|
|
226
|
+
<div
|
|
227
|
+
vdPopover
|
|
228
|
+
class="vd-time-picker"
|
|
229
|
+
[attr.aria-label]="label()"
|
|
230
|
+
(toggle)="toggled($event)"
|
|
231
|
+
(focusout)="leave($event)"
|
|
232
|
+
>
|
|
233
|
+
<div class="vd-time-picker__heading">{{ label() }}</div>
|
|
234
|
+
@if (options().length) {
|
|
235
|
+
<div class="vd-time-picker__columns">
|
|
236
|
+
@for (column of ['hours', 'minutes']; track column) {
|
|
237
|
+
<div class="vd-time-picker__column">
|
|
238
|
+
<div class="vd-time-picker__column-label">
|
|
239
|
+
{{ column === 'hours' ? hoursLabel() : minutesLabel() }}
|
|
240
|
+
</div>
|
|
241
|
+
<div
|
|
242
|
+
class="vd-time-picker__list"
|
|
243
|
+
role="listbox"
|
|
244
|
+
[attr.data-column]="column"
|
|
245
|
+
[attr.aria-label]="column === 'hours' ? hoursLabel() : minutesLabel()"
|
|
246
|
+
>
|
|
247
|
+
@for (part of parts(column); track part; let i = $index) {
|
|
248
|
+
<div
|
|
249
|
+
class="vd-time-picker__option"
|
|
250
|
+
role="option"
|
|
251
|
+
[attr.aria-selected]="part === (column === 'hours' ? hour() : minute())"
|
|
252
|
+
[attr.tabindex]="i === activeIndex(column) ? 0 : -1"
|
|
253
|
+
(keydown)="navigate($event, column)"
|
|
254
|
+
(focus)="setActive(column, i)"
|
|
255
|
+
(click)="pick(part, column)"
|
|
256
|
+
>
|
|
257
|
+
<span>{{ part }}</span
|
|
258
|
+
><vd-icon class="vd-time-picker__mark" name="check" />
|
|
259
|
+
</div>
|
|
260
|
+
}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
}
|
|
264
|
+
</div>
|
|
265
|
+
} @else {
|
|
266
|
+
<p class="vd-time-picker__empty" role="status">{{ emptyLabel() }}</p>
|
|
267
|
+
}
|
|
268
|
+
</div>
|
|
269
|
+
`
|
|
270
|
+
}]
|
|
271
|
+
}]
|
|
272
|
+
});
|
|
273
|
+
var VdTimeInput = class VdTimeInput extends VdInput {
|
|
274
|
+
pickerLabel = input(...ngDevMode ? [void 0, { debugName: "pickerLabel" }] : /* istanbul ignore next */ []);
|
|
275
|
+
hoursLabel = input(...ngDevMode ? [void 0, { debugName: "hoursLabel" }] : /* istanbul ignore next */ []);
|
|
276
|
+
minutesLabel = input(...ngDevMode ? [void 0, { debugName: "minutesLabel" }] : /* istanbul ignore next */ []);
|
|
277
|
+
emptyLabel = input(...ngDevMode ? [void 0, { debugName: "emptyLabel" }] : /* istanbul ignore next */ []);
|
|
278
|
+
t = vdTexts("timeInput", {
|
|
279
|
+
pickerLabel: this.pickerLabel,
|
|
280
|
+
hoursLabel: this.hoursLabel,
|
|
281
|
+
minutesLabel: this.minutesLabel,
|
|
282
|
+
emptyLabel: this.emptyLabel
|
|
283
|
+
});
|
|
284
|
+
open = signal(false, ...ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []);
|
|
285
|
+
panelId = signal(null, ...ngDevMode ? [{ debugName: "panelId" }] : /* istanbul ignore next */ []);
|
|
286
|
+
element = inject(ElementRef).nativeElement;
|
|
287
|
+
container = inject(ViewContainerRef);
|
|
288
|
+
picker;
|
|
289
|
+
constructor() {
|
|
290
|
+
super();
|
|
291
|
+
afterNextRender(() => {
|
|
292
|
+
this.picker = this.container.createComponent(VdTimePicker);
|
|
293
|
+
this.picker.changeDetectorRef.detectChanges();
|
|
294
|
+
const panel = this.picker.instance.panel;
|
|
295
|
+
const anchor = `--${panel.id}`;
|
|
296
|
+
this.element.style.setProperty("anchor-name", anchor);
|
|
297
|
+
panel.style.setProperty("position-anchor", anchor);
|
|
298
|
+
panel.setAttribute("tabindex", "-1");
|
|
299
|
+
this.panelId.set(panel.id);
|
|
300
|
+
this.picker.instance.onState = (open) => this.open.set(open);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
showPicker(event) {
|
|
304
|
+
if (this.element.disabled || this.element.readOnly || !this.picker) return;
|
|
305
|
+
event.preventDefault();
|
|
306
|
+
this.picker.instance.open(this.element, this.t().pickerLabel, this.t().emptyLabel, this.t().hoursLabel, this.t().minutesLabel);
|
|
307
|
+
this.picker.changeDetectorRef.detectChanges();
|
|
308
|
+
this.picker.instance.show();
|
|
309
|
+
}
|
|
310
|
+
onKeydown(event) {
|
|
311
|
+
if (event.altKey && event.key === "ArrowDown") this.showPicker(event);
|
|
312
|
+
}
|
|
313
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
314
|
+
minVersion: "12.0.0",
|
|
315
|
+
version: "22.2.1",
|
|
316
|
+
ngImport: i0,
|
|
317
|
+
type: VdTimeInput,
|
|
318
|
+
deps: [],
|
|
319
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
320
|
+
});
|
|
321
|
+
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
322
|
+
minVersion: "17.1.0",
|
|
323
|
+
version: "22.2.1",
|
|
324
|
+
type: VdTimeInput,
|
|
325
|
+
isStandalone: true,
|
|
326
|
+
selector: "input[type=time][vdTimeInput]",
|
|
327
|
+
inputs: {
|
|
328
|
+
pickerLabel: {
|
|
329
|
+
classPropertyName: "pickerLabel",
|
|
330
|
+
publicName: "pickerLabel",
|
|
331
|
+
isSignal: true,
|
|
332
|
+
isRequired: false,
|
|
333
|
+
transformFunction: null
|
|
334
|
+
},
|
|
335
|
+
hoursLabel: {
|
|
336
|
+
classPropertyName: "hoursLabel",
|
|
337
|
+
publicName: "hoursLabel",
|
|
338
|
+
isSignal: true,
|
|
339
|
+
isRequired: false,
|
|
340
|
+
transformFunction: null
|
|
341
|
+
},
|
|
342
|
+
minutesLabel: {
|
|
343
|
+
classPropertyName: "minutesLabel",
|
|
344
|
+
publicName: "minutesLabel",
|
|
345
|
+
isSignal: true,
|
|
346
|
+
isRequired: false,
|
|
347
|
+
transformFunction: null
|
|
348
|
+
},
|
|
349
|
+
emptyLabel: {
|
|
350
|
+
classPropertyName: "emptyLabel",
|
|
351
|
+
publicName: "emptyLabel",
|
|
352
|
+
isSignal: true,
|
|
353
|
+
isRequired: false,
|
|
354
|
+
transformFunction: null
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
host: {
|
|
358
|
+
attributes: { "aria-haspopup": "dialog" },
|
|
359
|
+
listeners: {
|
|
360
|
+
"click": "showPicker($event)",
|
|
361
|
+
"keydown": "onKeydown($event)"
|
|
362
|
+
},
|
|
363
|
+
properties: {
|
|
364
|
+
"attr.data-state": "open() ? \"open\" : \"closed\"",
|
|
365
|
+
"attr.aria-controls": "panelId()"
|
|
366
|
+
},
|
|
367
|
+
classAttribute: "vd-time-input"
|
|
368
|
+
},
|
|
369
|
+
usesInheritance: true,
|
|
370
|
+
ngImport: i0
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
374
|
+
minVersion: "12.0.0",
|
|
375
|
+
version: "22.2.1",
|
|
376
|
+
ngImport: i0,
|
|
377
|
+
type: VdTimeInput,
|
|
378
|
+
decorators: [{
|
|
379
|
+
type: Directive,
|
|
380
|
+
args: [{
|
|
381
|
+
selector: "input[type=time][vdTimeInput]",
|
|
382
|
+
host: {
|
|
383
|
+
class: "vd-time-input",
|
|
384
|
+
"aria-haspopup": "dialog",
|
|
385
|
+
"[attr.data-state]": "open() ? \"open\" : \"closed\"",
|
|
386
|
+
"[attr.aria-controls]": "panelId()",
|
|
387
|
+
"(click)": "showPicker($event)",
|
|
388
|
+
"(keydown)": "onKeydown($event)"
|
|
389
|
+
}
|
|
390
|
+
}]
|
|
391
|
+
}],
|
|
392
|
+
ctorParameters: () => [],
|
|
393
|
+
propDecorators: {
|
|
394
|
+
pickerLabel: [{
|
|
395
|
+
type: i0.Input,
|
|
396
|
+
args: [{
|
|
397
|
+
isSignal: true,
|
|
398
|
+
alias: "pickerLabel",
|
|
399
|
+
required: false
|
|
400
|
+
}]
|
|
401
|
+
}],
|
|
402
|
+
hoursLabel: [{
|
|
403
|
+
type: i0.Input,
|
|
404
|
+
args: [{
|
|
405
|
+
isSignal: true,
|
|
406
|
+
alias: "hoursLabel",
|
|
407
|
+
required: false
|
|
408
|
+
}]
|
|
409
|
+
}],
|
|
410
|
+
minutesLabel: [{
|
|
411
|
+
type: i0.Input,
|
|
412
|
+
args: [{
|
|
413
|
+
isSignal: true,
|
|
414
|
+
alias: "minutesLabel",
|
|
415
|
+
required: false
|
|
416
|
+
}]
|
|
417
|
+
}],
|
|
418
|
+
emptyLabel: [{
|
|
419
|
+
type: i0.Input,
|
|
420
|
+
args: [{
|
|
421
|
+
isSignal: true,
|
|
422
|
+
alias: "emptyLabel",
|
|
423
|
+
required: false
|
|
424
|
+
}]
|
|
425
|
+
}]
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
export { VdTimeInput };
|
|
429
|
+
|
|
430
|
+
//# sourceMappingURL=vadem-ui-time-input.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vadem-ui-time-input.mjs","names":[],"sources":["../../../projects/ui/time-input/src/core/time-options.ts","../../../projects/ui/time-input/src/time-picker.ts","../../../projects/ui/time-input/src/time-input.ts"],"sourcesContent":["/** Minute-resolution choices matching the native time input's periodic range and step base. */\nexport function timeOptions(min: string, max: string, step: string, initial: string): string[] {\n const seconds = (value: string): number | undefined => {\n if (!/^\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,3})?)?$/.test(value)) return undefined;\n const [h, m, s = 0] = value.split(':').map(Number);\n return h < 24 && m < 60 && s < 60 ? h * 3600 + m * 60 + s : undefined;\n };\n const low = seconds(min),\n high = seconds(max),\n base = low ?? seconds(initial) ?? 0;\n const increment = step === 'any' ? 0 : Number(step) > 0 ? Number(step) : 60;\n const result: string[] = [];\n for (let minute = 0; minute < 1440; minute++) {\n const value = minute * 60;\n const inRange =\n low !== undefined && high !== undefined && low > high\n ? value >= low || value <= high\n : (low === undefined || value >= low) && (high === undefined || value <= high);\n const quotient = increment ? (value - base) / increment : 0;\n if (inRange && Math.abs(quotient - Math.round(quotient)) < 1e-7)\n result.push(\n `${String(Math.floor(minute / 60)).padStart(2, '0')}:${String(minute % 60).padStart(2, '0')}`,\n );\n }\n return result;\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n ViewEncapsulation,\n inject,\n computed,\n ChangeDetectorRef,\n signal,\n} from '@angular/core';\nimport { VdIcon } from '@vadem/ui/icon';\nimport { VdPopover } from '@vadem/ui/popover';\nimport { timeOptions } from './core/time-options';\n\n/** Internal view; the input remains the sole form control and value owner. */\n@Component({\n selector: 'vd-time-picker',\n imports: [VdPopover, VdIcon],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n template: `\n <div\n vdPopover\n class=\"vd-time-picker\"\n [attr.aria-label]=\"label()\"\n (toggle)=\"toggled($event)\"\n (focusout)=\"leave($event)\"\n >\n <div class=\"vd-time-picker__heading\">{{ label() }}</div>\n @if (options().length) {\n <div class=\"vd-time-picker__columns\">\n @for (column of ['hours', 'minutes']; track column) {\n <div class=\"vd-time-picker__column\">\n <div class=\"vd-time-picker__column-label\">\n {{ column === 'hours' ? hoursLabel() : minutesLabel() }}\n </div>\n <div\n class=\"vd-time-picker__list\"\n role=\"listbox\"\n [attr.data-column]=\"column\"\n [attr.aria-label]=\"column === 'hours' ? hoursLabel() : minutesLabel()\"\n >\n @for (part of parts(column); track part; let i = $index) {\n <div\n class=\"vd-time-picker__option\"\n role=\"option\"\n [attr.aria-selected]=\"part === (column === 'hours' ? hour() : minute())\"\n [attr.tabindex]=\"i === activeIndex(column) ? 0 : -1\"\n (keydown)=\"navigate($event, column)\"\n (focus)=\"setActive(column, i)\"\n (click)=\"pick(part, column)\"\n >\n <span>{{ part }}</span\n ><vd-icon class=\"vd-time-picker__mark\" name=\"check\" />\n </div>\n }\n </div>\n </div>\n }\n </div>\n } @else {\n <p class=\"vd-time-picker__empty\" role=\"status\">{{ emptyLabel() }}</p>\n }\n </div>\n `,\n})\nexport class VdTimePicker {\n readonly label = signal('Choose time');\n readonly emptyLabel = signal('No available times');\n readonly options = signal<string[]>([]);\n readonly selected = signal('');\n readonly hoursLabel = signal('Hours');\n readonly minutesLabel = signal('Minutes');\n readonly hour = signal('');\n readonly minute = signal('');\n readonly activeHour = signal(0);\n readonly activeMinute = signal(0);\n readonly hours = computed(() => [...new Set(this.options().map((time) => time.slice(0, 2)))]);\n readonly minutes = computed(() =>\n this.options()\n .filter((time) => time.startsWith(this.hour() + ':'))\n .map((time) => time.slice(3)),\n );\n private readonly changeDetector = inject(ChangeDetectorRef);\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private target?: HTMLInputElement;\n onState?: (open: boolean) => void;\n get panel(): HTMLElement {\n return this.host.querySelector('.vd-time-picker')!;\n }\n\n open(\n target: HTMLInputElement,\n label: string,\n emptyLabel: string,\n hoursLabel: string,\n minutesLabel: string,\n ): void {\n this.target = target;\n this.label.set(label);\n this.hoursLabel.set(hoursLabel);\n this.minutesLabel.set(minutesLabel);\n this.emptyLabel.set(emptyLabel);\n this.options.set(\n timeOptions(target.min, target.max, target.step, target.getAttribute('value') ?? ''),\n );\n this.selected.set(target.value);\n const initial = this.options().includes(target.value)\n ? target.value\n : (this.options()[0] ?? '');\n this.hour.set(initial.slice(0, 2));\n this.minute.set(initial.slice(3));\n this.activeHour.set(Math.max(0, this.hours().indexOf(this.hour())));\n this.activeMinute.set(Math.max(0, this.minutes().indexOf(this.minute())));\n }\n show(): void {\n if (!this.target || this.panel.matches(':popover-open')) return;\n this.panel.showPopover({ source: this.target } as ShowPopoverOptions);\n this.focusActive('hours');\n }\n toggled(event: Event): void {\n this.onState?.((event as ToggleEvent).newState === 'open');\n }\n leave(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (\n next &&\n next !== this.target &&\n !this.panel.contains(next) &&\n this.panel.matches(':popover-open')\n )\n this.panel.hidePopover();\n }\n choose(time: string): void {\n const target = this.target;\n // Recheck live properties and constraints in case the form changed while open.\n if (\n !target ||\n target.disabled ||\n target.readOnly ||\n !timeOptions(\n target.min,\n target.max,\n target.step,\n target.getAttribute('value') ?? '',\n ).includes(time)\n )\n return;\n target.value = time;\n target.dispatchEvent(new Event('input', { bubbles: true }));\n target.dispatchEvent(new Event('change', { bubbles: true }));\n this.panel.hidePopover();\n target.focus();\n }\n parts(column: string): string[] {\n return column === 'hours' ? this.hours() : this.minutes();\n }\n activeIndex(column: string): number {\n return column === 'hours' ? this.activeHour() : this.activeMinute();\n }\n setActive(column: string, index: number): void {\n (column === 'hours' ? this.activeHour : this.activeMinute).set(index);\n }\n pick(part: string, column: string): void {\n if (column === 'minutes') {\n this.choose(`${this.hour()}:${part}`);\n return;\n }\n this.hour.set(part);\n if (!this.minutes().includes(this.minute())) this.minute.set(this.minutes()[0] ?? '');\n this.activeMinute.set(Math.max(0, this.minutes().indexOf(this.minute())));\n this.changeDetector.detectChanges();\n this.focusActive('minutes');\n }\n navigate(event: KeyboardEvent, column: string): void {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.pick(this.parts(column)[this.activeIndex(column)], column);\n return;\n }\n const size = this.parts(column).length;\n let next: number;\n switch (event.key) {\n case 'ArrowDown':\n next = Math.min(size - 1, this.activeIndex(column) + 1);\n break;\n case 'ArrowUp':\n next = Math.max(0, this.activeIndex(column) - 1);\n break;\n case 'Home':\n next = 0;\n break;\n case 'End':\n next = size - 1;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.setActive(column, next);\n this.focusActive(column);\n }\n private focusActive(column: string): void {\n const option = this.panel.querySelectorAll<HTMLElement>(\n `[data-column=${column}] [role=option]`,\n )[this.activeIndex(column)];\n if (option) {\n option.focus({ preventScroll: true });\n option.scrollIntoView({ block: 'nearest', behavior: 'instant' });\n } else this.panel.focus();\n }\n}\n","import {\n ComponentRef,\n Directive,\n ElementRef,\n ViewContainerRef,\n afterNextRender,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport { VdInput } from '@vadem/ui/input';\nimport { VdTimePicker } from './time-picker';\nimport { vdTexts } from '@vadem/ui/locale';\n\n/** Native editor and accessors with a themed, minute-resolution selection popover. */\n@Directive({\n selector: 'input[type=time][vdTimeInput]',\n host: {\n class: 'vd-time-input',\n 'aria-haspopup': 'dialog',\n '[attr.data-state]': 'open() ? \"open\" : \"closed\"',\n '[attr.aria-controls]': 'panelId()',\n '(click)': 'showPicker($event)',\n '(keydown)': 'onKeydown($event)',\n },\n})\nexport class VdTimeInput extends VdInput {\n readonly pickerLabel = input<string>();\n readonly hoursLabel = input<string>();\n readonly minutesLabel = input<string>();\n readonly emptyLabel = input<string>();\n /** Text inputs resolved against the active VD_LOCALE. */\n protected readonly t = vdTexts('timeInput', {\n pickerLabel: this.pickerLabel,\n hoursLabel: this.hoursLabel,\n minutesLabel: this.minutesLabel,\n emptyLabel: this.emptyLabel,\n });\n protected readonly open = signal(false);\n protected readonly panelId = signal<string | null>(null);\n private readonly element = inject<ElementRef<HTMLInputElement>>(ElementRef).nativeElement;\n private readonly container = inject(ViewContainerRef);\n private picker?: ComponentRef<VdTimePicker>;\n\n constructor() {\n super();\n afterNextRender(() => {\n this.picker = this.container.createComponent(VdTimePicker);\n this.picker.changeDetectorRef.detectChanges();\n const panel = this.picker.instance.panel;\n const anchor = `--${panel.id}`;\n this.element.style.setProperty('anchor-name', anchor);\n panel.style.setProperty('position-anchor', anchor);\n panel.setAttribute('tabindex', '-1');\n this.panelId.set(panel.id);\n this.picker.instance.onState = (open) => this.open.set(open);\n });\n }\n protected showPicker(event: Event): void {\n if (this.element.disabled || this.element.readOnly || !this.picker) return;\n event.preventDefault();\n this.picker.instance.open(\n this.element,\n this.t().pickerLabel,\n this.t().emptyLabel,\n this.t().hoursLabel,\n this.t().minutesLabel,\n );\n this.picker.changeDetectorRef.detectChanges();\n this.picker.instance.show();\n }\n protected onKeydown(event: KeyboardEvent): void {\n if (event.altKey && event.key === 'ArrowDown') this.showPicker(event);\n }\n}\n"],"mappings":";;;;;;AACA,SAAgB,YAAY,KAAa,KAAa,MAAc,SAAe;CACjF,MAAM,WAAW,UAAqC;EACpD,IAAI,CAAC,yCAAyC,KAAK,KAAK,GAAG,OAAO;EAClE,MAAM,CAAC,GAAG,GAAG,IAAI,KAAK,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;EACjD,OAAO,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK,IAAI,OAAO,IAAI,KAAK,IAAI;CAC9D;CACA,MAAM,MAAM,QAAQ,GAAG,GACrB,OAAO,QAAQ,GAAG,GAClB,OAAO,OAAO,QAAQ,OAAO,KAAK;CACpC,MAAM,YAAY,SAAS,QAAQ,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI;CACzE,MAAM,SAAmB,CAAA;CACzB,KAAK,IAAI,SAAS,GAAG,SAAS,MAAM,UAAU;EAC5C,MAAM,QAAQ,SAAS;EACvB,MAAM,UACJ,QAAQ,UAAa,SAAS,UAAa,MAAM,OAC7C,SAAS,OAAO,SAAS,QACxB,QAAQ,UAAa,SAAS,SAAS,SAAS,UAAa,SAAS;EAC7E,MAAM,WAAW,aAAa,QAAQ,QAAQ,YAAY;EAC1D,IAAI,WAAW,KAAK,IAAI,WAAW,KAAK,MAAM,QAAQ,CAAC,IAAI,MACzD,OAAO,KACL,GAAG,OAAO,KAAK,MAAM,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,EAAC,GAAI,OAAO,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,GAAG;CAEnG;CACA,OAAO;AACT;ACyCA,IAAa,eAAb,MAAa,aAAY;CACd,QAAQ,OAAO,eAAc,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAC7B,aAAa,OAAO,sBAAqB,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CACzC,UAAU,OAAiB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAC9B,WAAW,OAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CACrB,aAAa,OAAO,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAC5B,eAAe,OAAO,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAChC,OAAO,OAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CACjB,SAAS,OAAO,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CACnB,aAAa,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CACtB,eAAe,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CACxB,QAAQ,eAAe,CAAC,GAAG,IAAI,IAAI,KAAK,QAAO,CAAE,CAAC,KAAK,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CACpF,UAAU,eACjB,KAAK,QAAO,CAAE,CACX,QAAQ,SAAS,KAAK,WAAW,KAAK,KAAI,IAAK,GAAG,CAAC,CAAC,CACpD,KAAK,SAAS,KAAK,MAAM,CAAC,CAAC,GAC/B,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACgB,iBAAiB,OAAO,iBAAiB;CACzC,OAAO,OAAgC,UAAU,CAAC,CAAC;CAC5D;CACR;CACA,IAAI,QAAK;EACP,OAAO,KAAK,KAAK,cAAc,iBAAiB;CAClD;CAEA,KACE,QACA,OACA,YACA,YACA,cAAoB;EAEpB,KAAK,SAAS;EACd,KAAK,MAAM,IAAI,KAAK;EACpB,KAAK,WAAW,IAAI,UAAU;EAC9B,KAAK,aAAa,IAAI,YAAY;EAClC,KAAK,WAAW,IAAI,UAAU;EAC9B,KAAK,QAAQ,IACX,YAAY,OAAO,KAAK,OAAO,KAAK,OAAO,MAAM,OAAO,aAAa,OAAO,KAAK,EAAE,CAAC;EAEtF,KAAK,SAAS,IAAI,OAAO,KAAK;EAC9B,MAAM,UAAU,KAAK,QAAO,CAAE,CAAC,SAAS,OAAO,KAAK,IAChD,OAAO,QACN,KAAK,QAAO,CAAE,CAAC,MAAM;EAC1B,KAAK,KAAK,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC;EACjC,KAAK,OAAO,IAAI,QAAQ,MAAM,CAAC,CAAC;EAChC,KAAK,WAAW,IAAI,KAAK,IAAI,GAAG,KAAK,MAAK,CAAE,CAAC,QAAQ,KAAK,KAAI,CAAE,CAAC,CAAC;EAClE,KAAK,aAAa,IAAI,KAAK,IAAI,GAAG,KAAK,QAAO,CAAE,CAAC,QAAQ,KAAK,OAAM,CAAE,CAAC,CAAC;CAC1E;CACA,OAAI;EACF,IAAI,CAAC,KAAK,UAAU,KAAK,MAAM,QAAQ,eAAe,GAAG;EACzD,KAAK,MAAM,YAAY,EAAE,QAAQ,KAAK,OAAM,CAAwB;EACpE,KAAK,YAAY,OAAO;CAC1B;CACA,QAAQ,OAAY;EAClB,KAAK,UAAW,MAAsB,aAAa,MAAM;CAC3D;CACA,MAAM,OAAiB;EACrB,MAAM,OAAO,MAAM;EACnB,IACE,QACA,SAAS,KAAK,UACd,CAAC,KAAK,MAAM,SAAS,IAAI,KACzB,KAAK,MAAM,QAAQ,eAAe,GAElC,KAAK,MAAM,YAAW;CAC1B;CACA,OAAO,MAAY;EACjB,MAAM,SAAS,KAAK;EAEpB,IACE,CAAC,UACD,OAAO,YACP,OAAO,YACP,CAAC,YACC,OAAO,KACP,OAAO,KACP,OAAO,MACP,OAAO,aAAa,OAAO,KAAK,EAAE,CACnC,CAAC,SAAS,IAAI,GAEf;EACF,OAAO,QAAQ;EACf,OAAO,cAAc,IAAI,MAAM,SAAS,EAAE,SAAS,KAAI,CAAE,CAAC;EAC1D,OAAO,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAI,CAAE,CAAC;EAC3D,KAAK,MAAM,YAAW;EACtB,OAAO,MAAK;CACd;CACA,MAAM,QAAc;EAClB,OAAO,WAAW,UAAU,KAAK,MAAK,IAAK,KAAK,QAAO;CACzD;CACA,YAAY,QAAc;EACxB,OAAO,WAAW,UAAU,KAAK,WAAU,IAAK,KAAK,aAAY;CACnE;CACA,UAAU,QAAgB,OAAa;EACrC,CAAC,WAAW,UAAU,KAAK,aAAa,KAAK,aAAA,CAAc,IAAI,KAAK;CACtE;CACA,KAAK,MAAc,QAAc;EAC/B,IAAI,WAAW,WAAW;GACxB,KAAK,OAAO,GAAG,KAAK,KAAI,EAAE,GAAI,MAAM;GACpC;EACF;EACA,KAAK,KAAK,IAAI,IAAI;EAClB,IAAI,CAAC,KAAK,QAAO,CAAE,CAAC,SAAS,KAAK,OAAM,CAAE,GAAG,KAAK,OAAO,IAAI,KAAK,QAAO,CAAE,CAAC,MAAM,EAAE;EACpF,KAAK,aAAa,IAAI,KAAK,IAAI,GAAG,KAAK,QAAO,CAAE,CAAC,QAAQ,KAAK,OAAM,CAAE,CAAC,CAAC;EACxE,KAAK,eAAe,cAAa;EACjC,KAAK,YAAY,SAAS;CAC5B;CACA,SAAS,OAAsB,QAAc;EAC3C,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAc;GACpB,KAAK,KAAK,KAAK,MAAM,MAAM,CAAC,CAAC,KAAK,YAAY,MAAM,IAAI,MAAM;GAC9D;EACF;EACA,MAAM,OAAO,KAAK,MAAM,MAAM,CAAC,CAAC;EAChC,IAAI;EACJ,QAAQ,MAAM,KAAd;GACE,KAAK;IACH,OAAO,KAAK,IAAI,OAAO,GAAG,KAAK,YAAY,MAAM,IAAI,CAAC;IACtD;GACF,KAAK;IACH,OAAO,KAAK,IAAI,GAAG,KAAK,YAAY,MAAM,IAAI,CAAC;IAC/C;GACF,KAAK;IACH,OAAO;IACP;GACF,KAAK;IACH,OAAO,OAAO;IACd;GACF,SACE;EACJ;EACA,MAAM,eAAc;EACpB,KAAK,UAAU,QAAQ,IAAI;EAC3B,KAAK,YAAY,MAAM;CACzB;CACQ,YAAY,QAAc;EAChC,MAAM,SAAS,KAAK,MAAM,iBACxB,gBAAgB,OAAM,gBAAiB,CACxC,CAAC,KAAK,YAAY,MAAM;EACzB,IAAI,QAAQ;GACV,OAAO,MAAM,EAAE,eAAe,KAAI,CAAE;GACpC,OAAO,eAAe;IAAE,OAAO;IAAW,UAAU;GAAS,CAAE;EACjE,OAAO,KAAK,MAAM,MAAK;CACzB;CAhJW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAZ,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAY,cAAA;EAAA,UAAA;EAAA,UAAA;EAAA,UA9Cb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CT,UAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MA/CS;GAAS,UAAA;GAAA,QAAA,CAAA,WAAA;GAAA,UAAA,CAAA,WAAA;EAAA,GAAA;GAAA,MAAA;GAAA,MAAE;GAAM,UAAA;GAAA,QAAA,CAAA,QAAA,OAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAiDhB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAY,YAAA,CAAA;EAnDxB,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,SAAS,CAAC,WAAW,MAAM;GAC3B,iBAAiB,wBAAwB;GACzC,eAAe,kBAAkB;GACjC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CX,CAAA;;;ACvCD,IAAa,cAAb,MAAa,oBAAoB,QAAO;CAC7B,cAAc,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAC7B,aAAa,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAC5B,eAAe,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAC9B,aAAa,MAAe,GAAA,YAAA,CAAA,QAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAElB,IAAI,QAAQ,aAAa;EAC1C,aAAa,KAAK;EAClB,YAAY,KAAK;EACjB,cAAc,KAAK;EACnB,YAAY,KAAK;CAClB,CAAA;CACkB,OAAO,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CACpB,UAAU,OAAsB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACvC,UAAU,OAAqC,UAAU,CAAC,CAAC;CAC3D,YAAY,OAAO,gBAAgB;CAC5C;CAER,cAAA;EACE,MAAK;EACL,sBAAqB;GACnB,KAAK,SAAS,KAAK,UAAU,gBAAgB,YAAY;GACzD,KAAK,OAAO,kBAAkB,cAAa;GAC3C,MAAM,QAAQ,KAAK,OAAO,SAAS;GACnC,MAAM,SAAS,KAAK,MAAM;GAC1B,KAAK,QAAQ,MAAM,YAAY,eAAe,MAAM;GACpD,MAAM,MAAM,YAAY,mBAAmB,MAAM;GACjD,MAAM,aAAa,YAAY,IAAI;GACnC,KAAK,QAAQ,IAAI,MAAM,EAAE;GACzB,KAAK,OAAO,SAAS,WAAW,SAAS,KAAK,KAAK,IAAI,IAAI;EAC7D,CAAC;CACH;CACU,WAAW,OAAY;EAC/B,IAAI,KAAK,QAAQ,YAAY,KAAK,QAAQ,YAAY,CAAC,KAAK,QAAQ;EACpE,MAAM,eAAc;EACpB,KAAK,OAAO,SAAS,KACnB,KAAK,SACL,KAAK,EAAC,CAAE,CAAC,aACT,KAAK,EAAC,CAAE,CAAC,YACT,KAAK,EAAC,CAAE,CAAC,YACT,KAAK,EAAC,CAAE,CAAC,YAAY;EAEvB,KAAK,OAAO,kBAAkB,cAAa;EAC3C,KAAK,OAAO,SAAS,KAAI;CAC3B;CACU,UAAU,OAAoB;EACtC,IAAI,MAAM,UAAU,MAAM,QAAQ,aAAa,KAAK,WAAW,KAAK;CACtE;CA/CW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAW,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAX,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAW,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA;GAAA,YAAA,EAAA,iBAAA,SAAA;GAAA,WAAA;IAAA,SAAA;IAAA,WAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,sBAAA;GAAA;GAAA,gBAAA;EAAA;EAAA,iBAAA;EAAA,UAAA;CAAA,CAAA;;AAAX,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAW,YAAA,CAAA;EAXvB,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,MAAM;IACJ,OAAO;IACP,iBAAiB;IACjB,qBAAqB;IACrB,wBAAwB;IACxB,WAAW;IACX,aAAa;GACd;EACF,CAAA"}
|