kanto-ds 1.8.0 → 1.9.0
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/README.md +25 -18
- package/dist/AGENTS.md +157 -0
- package/dist/cdn/kanto.min.js +4182 -1024
- package/dist/chunks/block-Cci-Kff_.js +69 -0
- package/dist/chunks/block-Cci-Kff_.js.map +1 -0
- package/dist/chunks/defaults-yhaR6U65.js +70 -0
- package/dist/chunks/{defaults-Cpeg13O5.js.map → defaults-yhaR6U65.js.map} +1 -1
- package/dist/chunks/heading-DsnZsfg-.js +21 -0
- package/dist/chunks/heading-DsnZsfg-.js.map +1 -0
- package/dist/chunks/{index-BEWmAE0T.js → index-1ZFgagWW.js} +18 -7
- package/dist/chunks/index-1ZFgagWW.js.map +1 -0
- package/dist/chunks/indicator-B_Rs8F92.js +49 -0
- package/dist/chunks/indicator-B_Rs8F92.js.map +1 -0
- package/dist/chunks/{kt-calendar-D72xa8qr.js → kt-calendar-SK8eapuy.js} +3 -3
- package/dist/chunks/{kt-calendar-D72xa8qr.js.map → kt-calendar-SK8eapuy.js.map} +1 -1
- package/dist/chunks/kt-color-picker-BoezCAmx.js +365 -0
- package/dist/chunks/kt-color-picker-BoezCAmx.js.map +1 -0
- package/dist/chunks/{kt-element-COgh6Yr1.js → kt-element-DvWtPQ1Y.js} +2 -2
- package/dist/chunks/{kt-element-COgh6Yr1.js.map → kt-element-DvWtPQ1Y.js.map} +1 -1
- package/dist/chunks/motion-ChVUUO7O.js +28 -0
- package/dist/chunks/motion-ChVUUO7O.js.map +1 -0
- package/dist/chunks/{segmented-field-BCjCGF4I.js → segmented-field-T_Zo6lc1.js} +2 -2
- package/dist/chunks/{segmented-field-BCjCGF4I.js.map → segmented-field-T_Zo6lc1.js.map} +1 -1
- package/dist/chunks/{typed-date-Z3vcUJbb.js → typed-date-CaU0kXoY.js} +2 -2
- package/dist/chunks/{typed-date-Z3vcUJbb.js.map → typed-date-CaU0kXoY.js.map} +1 -1
- package/dist/components/blocks/kt-auth-form/kt-auth-form.d.ts +143 -0
- package/dist/components/blocks/kt-auth-form/kt-auth-form.js +368 -0
- package/dist/components/blocks/kt-auth-form/kt-auth-form.js.map +1 -0
- package/dist/components/blocks/kt-blog-post/kt-blog-post.d.ts +75 -0
- package/dist/components/blocks/kt-blog-post/kt-blog-post.js +285 -0
- package/dist/components/blocks/kt-blog-post/kt-blog-post.js.map +1 -0
- package/dist/components/blocks/kt-cta/kt-cta.d.ts +51 -0
- package/dist/components/blocks/kt-cta/kt-cta.js +126 -0
- package/dist/components/blocks/kt-cta/kt-cta.js.map +1 -0
- package/dist/components/blocks/kt-empty-page/kt-empty-page.d.ts +75 -0
- package/dist/components/blocks/kt-empty-page/kt-empty-page.js +245 -0
- package/dist/components/blocks/kt-empty-page/kt-empty-page.js.map +1 -0
- package/dist/components/blocks/kt-error-page/kt-error-page.d.ts +65 -0
- package/dist/components/blocks/kt-error-page/kt-error-page.js +177 -0
- package/dist/components/blocks/kt-error-page/kt-error-page.js.map +1 -0
- package/dist/components/blocks/kt-faq/kt-faq.d.ts +61 -0
- package/dist/components/blocks/kt-faq/kt-faq.js +129 -0
- package/dist/components/blocks/kt-faq/kt-faq.js.map +1 -0
- package/dist/components/blocks/kt-feature-grid/kt-feature-grid.d.ts +59 -0
- package/dist/components/blocks/kt-feature-grid/kt-feature-grid.js +166 -0
- package/dist/components/blocks/kt-feature-grid/kt-feature-grid.js.map +1 -0
- package/dist/components/blocks/kt-hero/kt-hero.d.ts +51 -0
- package/dist/components/blocks/kt-hero/kt-hero.js +138 -0
- package/dist/components/blocks/kt-hero/kt-hero.js.map +1 -0
- package/dist/components/blocks/kt-logo-cloud/kt-logo-cloud.d.ts +46 -0
- package/dist/components/blocks/kt-logo-cloud/kt-logo-cloud.js +100 -0
- package/dist/components/blocks/kt-logo-cloud/kt-logo-cloud.js.map +1 -0
- package/dist/components/blocks/kt-newsletter/kt-newsletter.d.ts +85 -0
- package/dist/components/blocks/kt-newsletter/kt-newsletter.js +247 -0
- package/dist/components/blocks/kt-newsletter/kt-newsletter.js.map +1 -0
- package/dist/components/blocks/kt-post-grid/kt-post-grid.d.ts +73 -0
- package/dist/components/blocks/kt-post-grid/kt-post-grid.js +249 -0
- package/dist/components/blocks/kt-post-grid/kt-post-grid.js.map +1 -0
- package/dist/components/blocks/kt-pricing-table/kt-pricing-table.d.ts +102 -0
- package/dist/components/blocks/kt-pricing-table/kt-pricing-table.js +307 -0
- package/dist/components/blocks/kt-pricing-table/kt-pricing-table.js.map +1 -0
- package/dist/components/blocks/kt-settings-section/kt-settings-section.d.ts +52 -0
- package/dist/components/blocks/kt-settings-section/kt-settings-section.js +158 -0
- package/dist/components/blocks/kt-settings-section/kt-settings-section.js.map +1 -0
- package/dist/components/blocks/kt-testimonials/kt-testimonials.d.ts +56 -0
- package/dist/components/blocks/kt-testimonials/kt-testimonials.js +161 -0
- package/dist/components/blocks/kt-testimonials/kt-testimonials.js.map +1 -0
- package/dist/components/core/kt-avatar/kt-avatar.js +2 -2
- package/dist/components/core/kt-avatar-group/kt-avatar-group.d.ts +42 -0
- package/dist/components/core/kt-avatar-group/kt-avatar-group.js +97 -0
- package/dist/components/core/kt-avatar-group/kt-avatar-group.js.map +1 -0
- package/dist/components/core/kt-badge/kt-badge.js +2 -2
- package/dist/components/core/kt-button/flights.d.ts +27 -0
- package/dist/components/core/kt-button/kt-button.d.ts +52 -1
- package/dist/components/core/kt-button/kt-button.js +353 -77
- package/dist/components/core/kt-button/kt-button.js.map +1 -1
- package/dist/components/core/kt-button/morph.d.ts +27 -0
- package/dist/components/core/kt-card/kt-card.js +1 -1
- package/dist/components/core/kt-code/kt-code.d.ts +2 -1
- package/dist/components/core/kt-code/kt-code.js +52 -37
- package/dist/components/core/kt-code/kt-code.js.map +1 -1
- package/dist/components/core/kt-copy-button/kt-copy-button.d.ts +47 -0
- package/dist/components/core/kt-copy-button/kt-copy-button.js +74 -0
- package/dist/components/core/kt-copy-button/kt-copy-button.js.map +1 -0
- package/dist/components/core/kt-icon/kt-icon.js +2 -2
- package/dist/components/core/kt-kbd/kt-kbd.js +1 -1
- package/dist/components/core/kt-split-button/kt-split-button.js +2 -2
- package/dist/components/data/kt-chart/kt-chart.js +2 -2
- package/dist/components/data/kt-chat-message/kt-chat-message.d.ts +60 -0
- package/dist/components/data/kt-chat-message/kt-chat-message.js +204 -0
- package/dist/components/data/kt-chat-message/kt-chat-message.js.map +1 -0
- package/dist/components/data/kt-description-list/kt-description-list.d.ts +56 -0
- package/dist/components/data/kt-description-list/kt-description-list.js +120 -0
- package/dist/components/data/kt-description-list/kt-description-list.js.map +1 -0
- package/dist/components/data/kt-meter/kt-meter.js +8 -6
- package/dist/components/data/kt-meter/kt-meter.js.map +1 -1
- package/dist/components/data/kt-pagination/kt-pagination.js +2 -2
- package/dist/components/data/kt-stat/kt-stat.d.ts +3 -1
- package/dist/components/data/kt-stat/kt-stat.js +31 -16
- package/dist/components/data/kt-stat/kt-stat.js.map +1 -1
- package/dist/components/data/kt-table/kt-table.js +2 -2
- package/dist/components/data/kt-timeline/kt-timeline.js +2 -2
- package/dist/components/data/kt-timeline/kt-timeline.js.map +1 -1
- package/dist/components/data/kt-tree/kt-tree.d.ts +69 -0
- package/dist/components/data/kt-tree/kt-tree.js +208 -0
- package/dist/components/data/kt-tree/kt-tree.js.map +1 -0
- package/dist/components/feedback/kt-alert/kt-alert.js +2 -2
- package/dist/components/feedback/kt-empty-state/kt-empty-state.js +1 -1
- package/dist/components/feedback/kt-progress-bar/kt-progress-bar.js +1 -1
- package/dist/components/feedback/kt-skeleton/kt-skeleton.js +1 -1
- package/dist/components/feedback/kt-toast/kt-toast.js +2 -2
- package/dist/components/feedback/kt-toast-container/kt-toast-container.d.ts +8 -1
- package/dist/components/feedback/kt-toast-container/kt-toast-container.js +65 -26
- package/dist/components/feedback/kt-toast-container/kt-toast-container.js.map +1 -1
- package/dist/components/feedback/kt-tooltip/kt-tooltip.js +1 -1
- package/dist/components/forms/kt-calendar/kt-calendar.js +3 -3
- package/dist/components/forms/kt-checkbox/kt-checkbox.d.ts +2 -0
- package/dist/components/forms/kt-checkbox/kt-checkbox.js +19 -8
- package/dist/components/forms/kt-checkbox/kt-checkbox.js.map +1 -1
- package/dist/components/forms/kt-color-picker/kt-color-picker.d.ts +66 -0
- package/dist/components/forms/kt-color-picker/kt-color-picker.js +14 -0
- package/dist/components/forms/kt-color-picker/kt-color-picker.js.map +1 -0
- package/dist/components/forms/kt-date-input/kt-date-input.js +5 -5
- package/dist/components/forms/kt-date-picker/kt-date-picker.js +4 -4
- package/dist/components/forms/kt-drag-drop/kt-drag-drop.js +2 -2
- package/dist/components/forms/kt-form/kt-form.js +2 -2
- package/dist/components/forms/kt-input/kt-input.js +2 -2
- package/dist/components/forms/kt-input-menu/kt-input-menu.js +2 -2
- package/dist/components/forms/kt-label-input/kt-label-input.js +1 -1
- package/dist/components/forms/kt-multi-select/kt-multi-select.js +2 -2
- package/dist/components/forms/kt-number-input/kt-number-input.d.ts +3 -0
- package/dist/components/forms/kt-number-input/kt-number-input.js +61 -45
- package/dist/components/forms/kt-number-input/kt-number-input.js.map +1 -1
- package/dist/components/forms/kt-otp-input/kt-otp-input.d.ts +74 -0
- package/dist/components/forms/kt-otp-input/kt-otp-input.js +232 -0
- package/dist/components/forms/kt-otp-input/kt-otp-input.js.map +1 -0
- package/dist/components/forms/kt-prompt-input/kt-prompt-input.d.ts +74 -0
- package/dist/components/forms/kt-prompt-input/kt-prompt-input.js +213 -0
- package/dist/components/forms/kt-prompt-input/kt-prompt-input.js.map +1 -0
- package/dist/components/forms/kt-radio-group/kt-radio-group.js +2 -2
- package/dist/components/forms/kt-select/kt-select.js +2 -2
- package/dist/components/forms/kt-slider/kt-slider.js +2 -2
- package/dist/components/forms/kt-textarea/kt-textarea.js +2 -2
- package/dist/components/forms/kt-time-input/kt-time-input.js +3 -3
- package/dist/components/forms/kt-toggle/kt-toggle.js +1 -1
- package/dist/components/navigation/kt-app-shell/kt-app-shell.d.ts +85 -0
- package/dist/components/navigation/kt-app-shell/kt-app-shell.js +220 -0
- package/dist/components/navigation/kt-app-shell/kt-app-shell.js.map +1 -0
- package/dist/components/navigation/kt-breadcrumb/kt-breadcrumb.js +2 -2
- package/dist/components/navigation/kt-footer/kt-footer.d.ts +82 -0
- package/dist/components/navigation/kt-footer/kt-footer.js +220 -0
- package/dist/components/navigation/kt-footer/kt-footer.js.map +1 -0
- package/dist/components/navigation/kt-header/kt-header.js +2 -2
- package/dist/components/navigation/kt-page-header/kt-page-header.js +2 -2
- package/dist/components/navigation/kt-page-header/kt-page-header.js.map +1 -1
- package/dist/components/navigation/kt-segmented-control/kt-segmented-control.d.ts +1 -0
- package/dist/components/navigation/kt-segmented-control/kt-segmented-control.js +81 -44
- package/dist/components/navigation/kt-segmented-control/kt-segmented-control.js.map +1 -1
- package/dist/components/navigation/kt-steps/kt-steps.d.ts +4 -1
- package/dist/components/navigation/kt-steps/kt-steps.js +80 -38
- package/dist/components/navigation/kt-steps/kt-steps.js.map +1 -1
- package/dist/components/navigation/kt-sub-menu-navigation/kt-sub-menu-navigation.js +3 -3
- package/dist/components/navigation/kt-sub-menu-navigation/kt-sub-menu-navigation.js.map +1 -1
- package/dist/components/navigation/kt-tabs/kt-tabs.d.ts +1 -0
- package/dist/components/navigation/kt-tabs/kt-tabs.js +92 -64
- package/dist/components/navigation/kt-tabs/kt-tabs.js.map +1 -1
- package/dist/components/navigation/kt-toggle-button/kt-toggle-button.js +1 -1
- package/dist/components/navigation/kt-toggle-button-group/kt-toggle-button-group.js +2 -2
- package/dist/components/navigation/kt-user-menu/kt-user-menu.d.ts +86 -0
- package/dist/components/navigation/kt-user-menu/kt-user-menu.js +298 -0
- package/dist/components/navigation/kt-user-menu/kt-user-menu.js.map +1 -0
- package/dist/components/overlays/kt-accordion/kt-accordion.d.ts +47 -0
- package/dist/components/overlays/kt-accordion/kt-accordion.js +80 -0
- package/dist/components/overlays/kt-accordion/kt-accordion.js.map +1 -0
- package/dist/components/overlays/kt-collapsible/kt-collapsible.d.ts +17 -1
- package/dist/components/overlays/kt-collapsible/kt-collapsible.js +87 -32
- package/dist/components/overlays/kt-collapsible/kt-collapsible.js.map +1 -1
- package/dist/components/overlays/kt-command-palette/kt-command-palette.js +2 -2
- package/dist/components/overlays/kt-confirm-dialog/kt-confirm-dialog.js +2 -2
- package/dist/components/overlays/kt-dropdown/kt-dropdown.js +2 -2
- package/dist/components/overlays/kt-modal/kt-modal.js +2 -2
- package/dist/components/overlays/kt-side-panel/kt-side-panel.js +2 -2
- package/dist/components/overlays/kt-tour/kt-tour.d.ts +77 -0
- package/dist/components/overlays/kt-tour/kt-tour.js +200 -0
- package/dist/components/overlays/kt-tour/kt-tour.js.map +1 -0
- package/dist/custom-elements.json +15655 -8814
- package/dist/docs/kt-accordion.md +49 -0
- package/dist/docs/kt-app-shell.md +89 -0
- package/dist/docs/kt-auth-form.md +111 -0
- package/dist/docs/kt-avatar-group.md +49 -0
- package/dist/docs/kt-blog-post.md +77 -0
- package/dist/docs/kt-button.md +21 -0
- package/dist/docs/kt-chat-message.md +68 -0
- package/dist/docs/kt-color-picker.md +67 -0
- package/dist/docs/kt-copy-button.md +50 -0
- package/dist/docs/kt-cta.md +45 -0
- package/dist/docs/kt-description-list.md +58 -0
- package/dist/docs/kt-empty-page.md +92 -0
- package/dist/docs/kt-error-page.md +64 -0
- package/dist/docs/kt-faq.md +54 -0
- package/dist/docs/kt-feature-grid.md +53 -0
- package/dist/docs/kt-footer.md +115 -0
- package/dist/docs/kt-hero.md +53 -0
- package/dist/docs/kt-logo-cloud.md +41 -0
- package/dist/docs/kt-newsletter.md +69 -0
- package/dist/docs/kt-otp-input.md +68 -0
- package/dist/docs/kt-post-grid.md +66 -0
- package/dist/docs/kt-pricing-table.md +101 -0
- package/dist/docs/kt-prompt-input.md +86 -0
- package/dist/docs/kt-settings-section.md +65 -0
- package/dist/docs/kt-testimonials.md +52 -0
- package/dist/docs/kt-tour.md +75 -0
- package/dist/docs/kt-tree.md +83 -0
- package/dist/docs/kt-user-menu.md +77 -0
- package/dist/icons/defaults.d.ts +2 -0
- package/dist/icons/index.js +1 -1
- package/dist/index.d.ts +50 -0
- package/dist/index.js +225 -292
- package/dist/index.js.map +1 -1
- package/dist/internal/block.d.ts +23 -0
- package/dist/internal/heading.d.ts +8 -0
- package/dist/internal/indicator.d.ts +34 -0
- package/dist/internal/motion.d.ts +25 -0
- package/dist/internal/strings.d.ts +22 -0
- package/dist/react/index.d.ts +134 -0
- package/dist/react/index.js +392 -178
- package/dist/react/index.js.map +1 -1
- package/dist/react/jsx.d.ts +102 -1
- package/dist/strings/index.js +1 -1
- package/dist/tokens/base.css +31 -2
- package/dist/vscode.css-custom-data.json +143 -21
- package/dist/vscode.html-custom-data.json +961 -195
- package/dist/vue/index.d.ts +54 -0
- package/dist/vue/index.js +35 -8
- package/dist/vue/index.js.map +1 -1
- package/dist/web-types.json +3006 -912
- package/package.json +110 -2
- package/dist/chunks/defaults-Cpeg13O5.js +0 -68
- package/dist/chunks/index-BEWmAE0T.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kt-color-picker-BoezCAmx.js","sources":["../../src/theme/accent.ts","../../src/components/forms/kt-color-picker/kt-color-picker.ts"],"sourcesContent":["/**\n * The accent colour: one colour in, every input the token layer reads out,\n * each at the contrast Kanto promises.\n *\n * Self-contained on purpose — no imports — so `scripts/generate-accents.js`\n * runs it straight from source under Node.\n */\n\n/** An sRGB colour, each channel 0–255. */\nexport type KtRgb = readonly [number, number, number];\ntype Oklch = readonly [number, number, number]; // L 0–1, C, H in degrees\n\nconst toLinear = (c: number) => {\n const v = c / 255;\n return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n};\nconst fromLinear = (c: number) =>\n 255 * (c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055);\n\nfunction toOklch([r, g, b]: KtRgb): Oklch {\n const [R, G, B] = [r, g, b].map(toLinear) as [number, number, number];\n const l = Math.cbrt(0.4122214708 * R + 0.5363325363 * G + 0.0514459929 * B);\n const m = Math.cbrt(0.2119034982 * R + 0.6806995451 * G + 0.1073969566 * B);\n const s = Math.cbrt(0.0883024619 * R + 0.2817188376 * G + 0.6299787005 * B);\n const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;\n const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;\n const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;\n return [L, Math.hypot(a, bb), ((Math.atan2(bb, a) * 180) / Math.PI + 360) % 360];\n}\n\n/** Unclamped: a channel outside 0–255 means the colour is outside sRGB. */\nfunction fromOklch([L, C, H]: Oklch): KtRgb {\n const a = C * Math.cos((H * Math.PI) / 180);\n const b = C * Math.sin((H * Math.PI) / 180);\n const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;\n const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;\n const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;\n return [\n fromLinear(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),\n fromLinear(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),\n fromLinear(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),\n ];\n}\n\nconst inGamut = (rgb: KtRgb) => rgb.every((c) => c >= -0.5 && c <= 255.5);\n\n/** The most chroma sRGB holds at this lightness and hue, up to `limit`. */\nfunction fitChroma(L: number, H: number, limit: number): number {\n if (inGamut(fromOklch([L, limit, H]))) return limit;\n let low = 0;\n let high = limit;\n for (let i = 0; i < 24; i += 1) {\n const mid = (low + high) / 2;\n if (inGamut(fromOklch([L, mid, H]))) low = mid;\n else high = mid;\n }\n return low;\n}\n\nconst round255 = (rgb: KtRgb): KtRgb =>\n rgb.map((c) => Math.min(255, Math.max(0, Math.round(c)))) as unknown as KtRgb;\n\nfunction luminance(rgb: KtRgb): number {\n const [r, g, b] = rgb.map(toLinear) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/** The WCAG contrast ratio of two colours. */\nexport function contrastRatio(a: KtRgb, b: KtRgb): number {\n const [high, low] = [luminance(a), luminance(b)].sort((x, y) => y - x) as [number, number];\n return (high + 0.05) / (low + 0.05);\n}\n\n/** `top` at `alpha` over `bottom`, blended in sRGB as browsers paint it. */\nconst over = (top: KtRgb, alpha: number, bottom: KtRgb): KtRgb =>\n top.map((c, i) => c * alpha + bottom[i]! * (1 - alpha)) as unknown as KtRgb;\n\nconst hex = (rgb: KtRgb) =>\n `#${round255(rgb)\n .map((c) => c.toString(16).padStart(2, '0'))\n .join('')}`;\n\n/** Reads `#rgb`, `#rrggbb`, `rgb()`/`rgba()` and `oklch()`; throws on anything else. */\nexport function parseColor(input: string): KtRgb {\n const text = input.trim().toLowerCase();\n let match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/.exec(text);\n if (match) {\n const digits = match[1]!.length === 3 ? [...match[1]!].map((d) => d + d).join('') : match[1]!;\n return [0, 2, 4].map((i) => parseInt(digits.slice(i, i + 2), 16)) as unknown as KtRgb;\n }\n match =\n /^rgba?\\(\\s*(\\d*\\.?\\d+)[\\s,]+(\\d*\\.?\\d+)[\\s,]+(\\d*\\.?\\d+)(?:\\s*[,/]\\s*\\d*\\.?\\d+%?)?\\s*\\)$/.exec(\n text,\n );\n if (match) {\n const rgb = match.slice(1, 4).map(Number) as unknown as KtRgb;\n if (rgb.every((c) => c <= 255)) return rgb;\n }\n match =\n /^oklch\\(\\s*(\\d*\\.?\\d+)(%?)\\s+(\\d*\\.?\\d+)\\s+(\\d*\\.?\\d+)(?:deg)?\\s*(?:\\/\\s*\\d*\\.?\\d+%?\\s*)?\\)$/.exec(\n text,\n );\n if (match) {\n const L = Number(match[1]) / (match[2] ? 100 : 1);\n const H = Number(match[4]);\n if (L <= 1) return round255(fromOklch([L, fitChroma(L, H, Number(match[3])), H]));\n }\n throw new TypeError(`Not a colour Kanto can read: \"${input}\". Use #rrggbb, rgb() or oklch().`);\n}\n\n/** What the token layer reads for an accent. */\nexport interface KtAccentPalette {\n /** Solid fill; carries white text at 4.5:1 or more. */\n readonly base: string;\n /** Fill on hover, a step darker. */\n readonly hover: string;\n /** The accent as text or an icon in the dark theme. */\n readonly textDark: string;\n /** The accent as text or an icon in the light theme. */\n readonly textLight: string;\n /** The translucent wash behind secondary buttons on hover: the fill at 16%. */\n readonly wash: string;\n /** The hue, in degrees, the neutral ramp is tinted with. */\n readonly neutralHue: number;\n /** How much of the ramp's tint to keep: 0 for a grey accent, 1 for a vivid one. */\n readonly neutralChroma: number;\n}\n\n/** The violet Kanto ships, as literal values: what the tokens fall back to. */\nexport const KT_DEFAULT_ACCENT: KtAccentPalette = {\n base: '#5f5dea',\n hover: '#5856e0',\n textDark: '#8e97ff',\n textLight: '#4f30ef',\n wash: 'rgba(111, 116, 246, 0.16)',\n neutralHue: 278,\n neutralChroma: 1,\n};\n\n/** The presets `accents.css` carries, each from a reference colour. */\nexport const KT_ACCENTS: readonly { id: string; label: string; color: string }[] = [\n { id: 'violet', label: 'Violet', color: '#5f5dea' },\n { id: 'blue', label: 'Blue', color: '#1f6feb' },\n { id: 'teal', label: 'Teal', color: '#0d9488' },\n { id: 'green', label: 'Green', color: '#16a34a' },\n { id: 'orange', label: 'Orange', color: '#ea580c' },\n { id: 'pink', label: 'Pink', color: '#db2777' },\n { id: 'slate', label: 'Slate', color: '#64748b' },\n];\n\n/** The neutral steps text reads on, as [L, C, hue offset] — `--color-dark-20` per theme. */\nconst DARK_GROUND: Oklch = [0.3074, 0.018, 16.5];\nconst LIGHT_GROUNDS: Oklch[] = [\n [1, 0, 0],\n [0.9023, 0.0081, 8.2],\n];\nconst WHITE_TEXT: KtRgb = [244, 244, 245];\n/** 4.5:1, with room for the rounding to hex and the browser's own. */\nconst TARGET = 4.6;\n\n/** Every token input for `color`, solved in OKLCH for the contrast Kanto promises. */\nexport function accentPalette(color: string): KtAccentPalette {\n const [inL, inC, inH] = toOklch(parseColor(color));\n const hue = inC < 0.0005 ? 0 : inH;\n const neutralChroma = Math.round(Math.min(1, inC / 0.08) * 100) / 100;\n const at = (L: number): KtRgb => fromOklch([L, fitChroma(L, hue, inC), hue]);\n const ground = ([L, C, offset]: Oklch): KtRgb => fromOklch([L, C * neutralChroma, hue + offset]);\n\n // The lightest fill, at or below the colour's own lightness, that white reads on.\n let baseL = Math.min(inL, 0.72);\n while (baseL > 0 && contrastRatio(WHITE_TEXT, at(baseL)) < TARGET) baseL -= 0.0025;\n const base = at(baseL);\n\n // Dark theme text: the darkest step that reads, which keeps the most colour.\n // Accent text sits on the surfaces, on its own 12% tint and on the 16%\n // wash behind a hovered secondary button.\n const tints = (g: KtRgb) => [g, over(base, 0.12, g), over(base, 0.16, g)];\n const darkGrounds = tints(ground(DARK_GROUND));\n let darkL = 0.45;\n while (darkL < 1 && darkGrounds.some((g) => contrastRatio(at(darkL), g) < TARGET)) {\n darkL += 0.0025;\n }\n\n // Light theme text: the lightest step that reads.\n const lightGrounds = LIGHT_GROUNDS.map(ground).flatMap(tints);\n let lightL = 0.7;\n while (lightL > 0 && lightGrounds.some((g) => contrastRatio(at(lightL), g) < TARGET)) {\n lightL -= 0.0025;\n }\n\n const [r, g, b] = round255(base);\n return {\n base: hex(base),\n hover: hex(at(Math.max(0, baseL - 0.025))),\n textDark: hex(at(darkL)),\n textLight: hex(at(lightL)),\n wash: `rgba(${r}, ${g}, ${b}, 0.16)`,\n neutralHue: Math.round(hue * 10) / 10,\n neutralChroma,\n };\n}\n\n/** The custom properties a palette sets — what `accents.css` and `setAccent` write. */\nexport function accentProperties(palette: KtAccentPalette): Record<string, string> {\n return {\n '--accent-base': palette.base,\n '--accent-hover': palette.hover,\n '--accent-text-dark': palette.textDark,\n '--accent-text-light': palette.textLight,\n '--accent-wash': palette.wash,\n '--neutral-hue': String(palette.neutralHue),\n '--neutral-chroma': String(palette.neutralChroma),\n };\n}\n\nconst INPUTS = Object.keys(accentProperties(KT_DEFAULT_ACCENT));\n\n/**\n * Themes `root` — the page by default — with any colour; `null` takes it off,\n * back to the `data-accent` preset or the default.\n *\n * The inputs go on as inline custom properties. The element also needs a\n * `data-accent` attribute for the token layer to re-read them there: a preset\n * already on it is kept, otherwise it gets `data-accent=\"custom\"`.\n */\nexport function setAccent(\n color: string | null,\n root: HTMLElement = document.documentElement,\n): void {\n if (color === null) {\n for (const name of INPUTS) root.style.removeProperty(name);\n if (root.dataset['accent'] === 'custom') delete root.dataset['accent'];\n return;\n }\n for (const [name, value] of Object.entries(accentProperties(accentPalette(color)))) {\n root.style.setProperty(name, value);\n }\n // A preset already there stays: the inline inputs win over it, and it is\n // what setAccent(null) goes back to.\n root.dataset['accent'] ??= 'custom';\n}\n","import { css, html, nothing, type PropertyValues, type TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { live } from 'lit/directives/live.js';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { KtElement, defineElement } from '#internal/kt-element';\nimport { emit } from '#internal/events';\nimport { attachFormInternals, setFormValue, type UsableInternals } from '#internal/form-control';\nimport { strings } from '#internal/strings';\nimport { KT_ACCENTS } from '../../../theme/accent.js';\nimport '../../core/kt-icon/kt-icon.js';\n\nexport interface KtColorSwatch {\n /** A hex colour: `#1f6feb`. */\n readonly value: string;\n /** Its name, said for the swatch: \"Blue\". Defaults to the hex code. */\n readonly label?: string;\n}\n\nconst HEX = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;\n\n/** A hex colour in its one spelling — `#rrggbb`, lower case — or null when it is not one. */\nfunction normalise(text: string): string | null {\n const match = HEX.exec(text.trim());\n if (!match) return null;\n const digits = match[1]!.toLowerCase();\n const full = digits.length === 3 ? [...digits].map((d) => d + d).join('') : digits;\n return `#${full}`;\n}\n\n/**\n * A colour, chosen from swatches or set freely: a label's colour, a theme's\n * accent, a chart series.\n *\n * The swatches are a radio group — the arrows move and pick, wrapping — Kanto's\n * accents until `swatches` gives others. Beside them, the free colour: the\n * system's own picker behind a swatch, and a hex field that takes a colour\n * once it is a whole one. `no-custom` keeps to the swatches.\n *\n * A form control, submitted as `#rrggbb` under `name`.\n *\n * @element kt-color-picker\n *\n * @csspart swatches - The radio group.\n * @csspart swatch - One swatch.\n * @csspart custom - The free colour's swatch.\n * @csspart hex - The hex field.\n *\n * @fires kt-change - The colour changed. `detail: { value }`.\n *\n * @example\n * ```html\n * <kt-color-picker label=\"Label colour\" name=\"colour\" value=\"#1f6feb\"></kt-color-picker>\n * ```\n */\nexport class KtColorPicker extends KtElement {\n static readonly formAssociated = true;\n\n static override styles = [\n KtElement.styles,\n css`\n :host {\n display: inline-block;\n --kt-swatch-size: 24px;\n }\n\n .picker {\n display: flex;\n flex-wrap: wrap;\n gap: var(--gap-form);\n align-items: center;\n }\n\n .swatches {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n }\n\n .swatch {\n position: relative;\n display: inline-grid;\n flex: none;\n place-items: center;\n width: var(--kt-swatch-size);\n height: var(--kt-swatch-size);\n padding: 0;\n color: var(--text-muted);\n background: var(--swatch, var(--surface-field));\n border: var(--border-width) solid var(--border-subtle);\n border-radius: var(--radius-full);\n cursor: pointer;\n transition: box-shadow var(--duration-fast) var(--easing-standard);\n }\n /* Chosen: a ring in its own colour, set off by the page's. */\n .swatch[aria-checked='true'],\n .swatch[data-chosen] {\n box-shadow:\n 0 0 0 2px var(--surface-page),\n 0 0 0 4px var(--swatch, var(--color-primary-base));\n }\n .swatch:focus-visible {\n outline: var(--outline-width) solid var(--color-primary-base);\n outline-offset: 5px;\n }\n :host([disabled]) .swatch {\n cursor: not-allowed;\n opacity: 0.5;\n }\n\n /* The system picker, invisible over the swatch that stands for it. */\n .custom input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n opacity: 0;\n cursor: inherit;\n }\n\n .hex {\n box-sizing: border-box;\n width: 96px;\n height: var(--button-height-small);\n padding: 0 10px;\n color: var(--text-body);\n font: var(--font-code-regular);\n text-transform: lowercase;\n background: var(--surface-field);\n border: var(--border-width) solid var(--border-field);\n border-radius: var(--radius-input);\n outline: var(--outline-width) solid transparent;\n transition: outline-color var(--duration-instant);\n }\n .hex:focus {\n outline-color: var(--color-primary-base);\n }\n `,\n ];\n\n /** The colour, as `#rrggbb`. */\n @property({ type: String })\n value = '';\n\n /** The preset colours. Kanto's accents until given others. */\n @property({ attribute: false })\n swatches: readonly KtColorSwatch[] = KT_ACCENTS.map((accent) => ({\n value: accent.color,\n label: accent.label,\n }));\n\n /** Accessible name for the swatches: \"Label colour\". */\n @property({ type: String })\n label = '';\n\n @property({ type: String })\n name = '';\n\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /** Keeps to the swatches: no system picker, no hex field. */\n @property({ type: Boolean, attribute: 'no-custom' })\n noCustom = false;\n\n @state() private formDisabled = false;\n\n private internals: UsableInternals | null = null;\n private defaultValue = '';\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.internals ??= attachFormInternals(this);\n this.value = normalise(this.value) ?? '';\n this.defaultValue = this.value;\n }\n\n override willUpdate(changed: PropertyValues<this>): void {\n if (changed.has('value')) {\n const value = normalise(this.value) ?? '';\n if (value !== this.value) this.value = value;\n setFormValue(this.internals, this.value || null);\n }\n }\n\n formResetCallback(): void {\n this.value = this.defaultValue;\n }\n\n formDisabledCallback(disabled: boolean): void {\n this.formDisabled = disabled;\n }\n\n formStateRestoreCallback(state: string | null): void {\n this.value = state ?? '';\n }\n\n private get inactive(): boolean {\n return this.disabled || this.formDisabled;\n }\n\n private get presets(): KtColorSwatch[] {\n return this.swatches.flatMap((swatch) => {\n const value = normalise(swatch.value);\n if (!value) return [];\n return swatch.label === undefined ? [{ value }] : [{ value, label: swatch.label }];\n });\n }\n\n private pick(value: string | null): void {\n if (!value || value === this.value || this.inactive) return;\n this.value = value;\n emit(this, 'kt-change', { value });\n }\n\n private onKeyDown(event: KeyboardEvent, index: number): void {\n const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[event.key];\n if (step === undefined) return;\n event.preventDefault();\n const presets = this.presets;\n const next = presets[(index + step + presets.length) % presets.length]!;\n this.pick(next.value);\n void this.updateComplete.then(() =>\n this.shadowRoot\n ?.querySelectorAll<HTMLElement>('[role=\"radio\"]')\n [presets.indexOf(next)]?.focus(),\n );\n }\n\n override render(): TemplateResult {\n const s = strings();\n const presets = this.presets;\n const chosen = presets.findIndex((swatch) => swatch.value === this.value);\n const custom = chosen === -1 && Boolean(this.value);\n return html`<div class=\"picker\">\n <div\n part=\"swatches\"\n class=\"swatches\"\n role=\"radiogroup\"\n aria-label=${this.label || nothing}\n aria-disabled=${this.inactive ? 'true' : nothing}\n >\n ${presets.map(\n (swatch, index) =>\n html`<button\n part=\"swatch\"\n class=\"swatch\"\n type=\"button\"\n role=\"radio\"\n aria-label=${swatch.label ?? swatch.value}\n aria-checked=${index === chosen ? 'true' : 'false'}\n tabindex=${index === (chosen === -1 ? 0 : chosen) ? 0 : -1}\n ?disabled=${this.inactive}\n style=${styleMap({ '--swatch': swatch.value })}\n @click=${() => this.pick(swatch.value)}\n @keydown=${(event: KeyboardEvent) => this.onKeyDown(event, index)}\n ></button>`,\n )}\n </div>\n ${\n this.noCustom\n ? nothing\n : html`<label\n part=\"custom\"\n class=\"swatch custom\"\n ?data-chosen=${custom}\n style=${styleMap({ '--swatch': custom ? this.value : undefined })}\n >\n ${custom ? nothing : html`<kt-icon name=\"plus\" size=\"14\"></kt-icon>`}\n <input\n type=\"color\"\n aria-label=${s.customColour}\n .value=${live(this.value || presets[0]?.value || '#000000')}\n ?disabled=${this.inactive}\n @input=${(event: Event) => this.pick(normalise((event.target as HTMLInputElement).value))}\n />\n </label>\n <input\n part=\"hex\"\n class=\"hex\"\n type=\"text\"\n inputmode=\"text\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n maxlength=\"7\"\n aria-label=${s.hexColour}\n .value=${live(this.value)}\n ?disabled=${this.inactive}\n @input=${(event: Event) => this.pick(normalise((event.target as HTMLInputElement).value))}\n />`\n }\n </div>`;\n }\n}\n\ndefineElement('kt-color-picker', KtColorPicker);\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'kt-color-picker': KtColorPicker;\n }\n}\n"],"names":["toLinear","c","v","fromLinear","toOklch","r","g","b","R","G","B","l","m","s","L","a","bb","fromOklch","C","H","inGamut","rgb","fitChroma","limit","low","high","i","mid","round255","luminance","contrastRatio","x","y","over","top","alpha","bottom","hex","parseColor","input","text","match","digits","d","KT_DEFAULT_ACCENT","KT_ACCENTS","DARK_GROUND","LIGHT_GROUNDS","WHITE_TEXT","TARGET","accentPalette","color","inL","inC","inH","hue","neutralChroma","at","ground","offset","baseL","base","tints","darkGrounds","darkL","lightGrounds","lightL","accentProperties","palette","INPUTS","setAccent","root","name","value","HEX","normalise","KtColorPicker","KtElement","accent","css","attachFormInternals","changed","setFormValue","disabled","state","swatch","emit","event","index","step","presets","next","strings","chosen","custom","html","nothing","styleMap","live","__decorateClass","property","defineElement"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LitElement as n, css as i } from "lit";
|
|
2
|
-
import { o } from "./index-
|
|
2
|
+
import { o } from "./index-1ZFgagWW.js";
|
|
3
3
|
const t = "kt-strings";
|
|
4
4
|
class c extends n {
|
|
5
5
|
static {
|
|
@@ -49,4 +49,4 @@ export {
|
|
|
49
49
|
c as K,
|
|
50
50
|
d
|
|
51
51
|
};
|
|
52
|
-
//# sourceMappingURL=kt-element-
|
|
52
|
+
//# sourceMappingURL=kt-element-DvWtPQ1Y.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kt-element-
|
|
1
|
+
{"version":3,"file":"kt-element-DvWtPQ1Y.js","sources":["../../src/internal/kt-element.ts"],"sourcesContent":["import { LitElement, css, type CSSResultGroup, type PropertyValues } from 'lit';\nimport { onStringsChange } from './strings.js';\n\n/**\n * The key a `setStrings` call marks in `changedProperties`, so an element can\n * recompute copy it caches outside `render()` — a validation message.\n */\nconst STRINGS_CHANGED = 'kt-strings';\n\n/**\n * Base class for every Kanto element.\n *\n * It exists for two reasons. First, `box-sizing: border-box` does not cross a\n * shadow boundary, so each root has to re-establish it. Second, `[hidden]`\n * loses to a `:host { display: flex }` rule inside the shadow root, and every\n * element in this system sets its own display — without the override here,\n * hiding a Kanto element from the outside silently does nothing.\n */\nexport class KtElement extends LitElement {\n static override styles: CSSResultGroup = css`\n :host {\n box-sizing: border-box;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n *,\n *::before,\n *::after {\n box-sizing: inherit;\n }\n\n /* Read by assistive technology, never drawn. */\n .visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n }\n `;\n\n private unsubscribeStrings: (() => void) | undefined;\n\n override connectedCallback(): void {\n super.connectedCallback();\n // Copy comes from the strings registry at render time; follow it when an\n // application switches language.\n // A fresh old value, because Lit skips a named update whose value did not\n // change — and 'kt-strings' is not a property, so it is always undefined.\n this.unsubscribeStrings = onStringsChange(() =>\n this.requestUpdate(STRINGS_CHANGED, Symbol('previous strings')),\n );\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.unsubscribeStrings?.();\n this.unsubscribeStrings = undefined;\n }\n\n /** True when this update was triggered by `setStrings`. */\n protected stringsChanged(changed: PropertyValues): boolean {\n return changed.has(STRINGS_CHANGED);\n }\n}\n\n/**\n * Registers a custom element, tolerating a repeat call.\n *\n * A design system gets loaded twice more often than you would like — two\n * versions in one dependency tree, a dev-server hot reload, a micro-frontend\n * mounting a second copy. `customElements.define` throws on a duplicate tag\n * and would take the whole page down with it.\n */\nexport function defineElement(tag: string, ctor: CustomElementConstructor): void {\n if (customElements.get(tag)) return;\n customElements.define(tag, ctor);\n}\n"],"names":["STRINGS_CHANGED","KtElement","LitElement","css","onStringsChange","changed","defineElement","tag","ctor"],"mappings":";;AAOA,MAAMA,IAAkB;AAWjB,MAAMC,UAAkBC,EAAW;AAAA,EACxC,OAAA;AAAA,SAAgB,SAAyBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA;AAAA,EA4BhC,oBAA0B;AACjC,UAAM,kBAAA,GAKN,KAAK,qBAAqBC;AAAA,MAAgB,MACxC,KAAK,cAAcJ,GAAiB,uBAAO,kBAAkB,CAAC;AAAA,IAAA;AAAA,EAElE;AAAA,EAES,uBAA6B;AACpC,UAAM,qBAAA,GACN,KAAK,qBAAA,GACL,KAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA,EAGU,eAAeK,GAAkC;AACzD,WAAOA,EAAQ,IAAIL,CAAe;AAAA,EACpC;AACF;AAUO,SAASM,EAAcC,GAAaC,GAAsC;AAC/E,EAAI,eAAe,IAAID,CAAG,KAC1B,eAAe,OAAOA,GAAKC,CAAI;AACjC;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function u(n) {
|
|
2
|
+
const t = /^\s*(\d*\.?\d+)(ms|s)\s*$/.exec(n);
|
|
3
|
+
if (!t) return 0;
|
|
4
|
+
const a = Number(t[1]);
|
|
5
|
+
return t[2] === "s" ? a * 1e3 : a;
|
|
6
|
+
}
|
|
7
|
+
function c(n, t) {
|
|
8
|
+
return u(getComputedStyle(n).getPropertyValue(t));
|
|
9
|
+
}
|
|
10
|
+
function f(n) {
|
|
11
|
+
return getComputedStyle(n).getPropertyValue("--easing-standard").trim() || "ease";
|
|
12
|
+
}
|
|
13
|
+
function d(n, t, a, e, r = {}) {
|
|
14
|
+
const i = c(n, a) * (r.scale ?? 1);
|
|
15
|
+
if (i <= 0 || typeof n.animate != "function") return null;
|
|
16
|
+
for (const s of n.getAnimations()) s.id === e && s.cancel();
|
|
17
|
+
const o = n.animate(t, {
|
|
18
|
+
duration: i,
|
|
19
|
+
easing: r.easing ?? f(n)
|
|
20
|
+
});
|
|
21
|
+
return o.id = e, o;
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
c as d,
|
|
25
|
+
f as e,
|
|
26
|
+
d as p
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=motion-ChVUUO7O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion-ChVUUO7O.js","sources":["../../src/internal/motion.ts"],"sourcesContent":["/**\n * Motion read from the theme. The duration tokens are where reduced motion is\n * honoured — they are all zero under `prefers-reduced-motion: reduce` — so\n * script-driven animations read them instead of carrying their own numbers,\n * and are skipped outright when they come back zero.\n */\n\n/** A CSS time — `0.2s`, `160ms` — in milliseconds; zero when it is not one. */\nexport function parseTime(value: string): number {\n const match = /^\\s*(\\d*\\.?\\d+)(ms|s)\\s*$/.exec(value);\n if (!match) return 0;\n const amount = Number(match[1]);\n return match[2] === 's' ? amount * 1000 : amount;\n}\n\n/** A duration token's value at `element`, in milliseconds. */\nexport function durationOf(element: Element, token: `--duration-${string}`): number {\n return parseTime(getComputedStyle(element).getPropertyValue(token));\n}\n\n/** The theme's standard easing at `element`, for the Web Animations API. */\nexport function easingOf(element: Element): string {\n return getComputedStyle(element).getPropertyValue('--easing-standard').trim() || 'ease';\n}\n\n/**\n * Plays `keyframes` on `element` for as long as the duration token says,\n * with the theme's easing, under `id` so tests and callers can find it.\n * Cancels an earlier run with the same id, so a quick second change starts\n * afresh. Null — and nothing played — when the token is zero or the browser\n * has no Web Animations.\n */\nexport function play(\n element: Element,\n keyframes: Keyframe[],\n token: `--duration-${string}`,\n id: string,\n options: {\n /** Overrides the theme's easing — `linear` when the keyframes carry their own. */\n readonly easing?: string;\n /** Multiplies the token's duration, for a motion with several legs. */\n readonly scale?: number;\n } = {},\n): Animation | null {\n const duration = durationOf(element, token) * (options.scale ?? 1);\n if (duration <= 0 || typeof element.animate !== 'function') return null;\n for (const running of element.getAnimations()) if (running.id === id) running.cancel();\n const animation = element.animate(keyframes, {\n duration,\n easing: options.easing ?? easingOf(element),\n });\n animation.id = id;\n return animation;\n}\n"],"names":["parseTime","value","match","amount","durationOf","element","token","easingOf","play","keyframes","id","options","duration","running","animation"],"mappings":"AAQO,SAASA,EAAUC,GAAuB;AAC/C,QAAMC,IAAQ,4BAA4B,KAAKD,CAAK;AACpD,MAAI,CAACC,EAAO,QAAO;AACnB,QAAMC,IAAS,OAAOD,EAAM,CAAC,CAAC;AAC9B,SAAOA,EAAM,CAAC,MAAM,MAAMC,IAAS,MAAOA;AAC5C;AAGO,SAASC,EAAWC,GAAkBC,GAAuC;AAClF,SAAON,EAAU,iBAAiBK,CAAO,EAAE,iBAAiBC,CAAK,CAAC;AACpE;AAGO,SAASC,EAASF,GAA0B;AACjD,SAAO,iBAAiBA,CAAO,EAAE,iBAAiB,mBAAmB,EAAE,UAAU;AACnF;AASO,SAASG,EACdH,GACAI,GACAH,GACAI,GACAC,IAKI,IACc;AAClB,QAAMC,IAAWR,EAAWC,GAASC,CAAK,KAAKK,EAAQ,SAAS;AAChE,MAAIC,KAAY,KAAK,OAAOP,EAAQ,WAAY,WAAY,QAAO;AACnE,aAAWQ,KAAWR,EAAQ,cAAA,GAAqBQ,EAAQ,OAAOH,KAAIG,EAAQ,OAAA;AAC9E,QAAMC,IAAYT,EAAQ,QAAQI,GAAW;AAAA,IAC3C,UAAAG;AAAA,IACA,QAAQD,EAAQ,UAAUJ,EAASF,CAAO;AAAA,EAAA,CAC3C;AACD,SAAAS,EAAU,KAAKJ,GACRI;AACT;"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { css as b, nothing as h, html as p } from "lit";
|
|
2
2
|
import { state as d, property as u } from "lit/decorators.js";
|
|
3
3
|
import { classMap as y } from "lit/directives/class-map.js";
|
|
4
|
-
import { K as v } from "./kt-element-
|
|
4
|
+
import { K as v } from "./kt-element-DvWtPQ1Y.js";
|
|
5
5
|
import { u as g, e as k } from "./events-r51msXNf.js";
|
|
6
6
|
import { a as w, s as $, b as S } from "./form-control-B74_EHtG.js";
|
|
7
7
|
import { r as K } from "./locale-C5xLkGx5.js";
|
|
@@ -381,4 +381,4 @@ n([
|
|
|
381
381
|
export {
|
|
382
382
|
c as K
|
|
383
383
|
};
|
|
384
|
-
//# sourceMappingURL=segmented-field-
|
|
384
|
+
//# sourceMappingURL=segmented-field-T_Zo6lc1.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segmented-field-BCjCGF4I.js","sources":["../../src/internal/segments.ts","../../src/internal/segmented-field.ts"],"sourcesContent":["/**\n * The arithmetic of a segmented field — a day, a month, a year, an hour —\n * each typed digit by digit and stepped with the arrows. Pure, so the two\n * fields that share it, `<kt-date-input>` and `<kt-time-input>`, agree on\n * every key.\n */\n\nexport interface SegmentSpec {\n readonly min: number;\n readonly max: number;\n /** Digits when full: 2 for a day, 4 for a year. */\n readonly length: number;\n}\n\n/**\n * The segment's text once `digit` is typed, and whether the focus should move\n * on. It moves on when the segment is full, or as soon as no further digit\n * could follow — a day starting with 4 is the 4th. A digit that would\n * overshoot the maximum, or one typed into a full segment, starts it over.\n */\nexport function typeDigit(\n current: string,\n digit: string,\n spec: SegmentSpec,\n): { text: string; advance: boolean } {\n let text = current.length >= spec.length ? digit : current + digit;\n if (Number(text) > spec.max) text = digit;\n const advance = text.length >= spec.length || Number(text) * 10 > spec.max;\n return { text, advance };\n}\n\n/**\n * The value an arrow press lands on: the next multiple of the step — counted\n * from the minimum — in the direction of `by`, wrapping past either end. An\n * empty segment starts from its first value going up, its last going down.\n */\nexport function stepSegment(value: number | null, by: number, spec: SegmentSpec): number {\n const step = Math.abs(by) || 1;\n const first = spec.min;\n const last = spec.min + Math.floor((spec.max - spec.min) / step) * step;\n if (value === null) return by > 0 ? first : last;\n\n const offset = value - spec.min;\n if (by > 0) {\n const next = spec.min + (Math.floor(offset / step) + 1) * step;\n return next > spec.max ? first : next;\n }\n const next = offset % step === 0 ? value - step : spec.min + Math.floor(offset / step) * step;\n return next < spec.min ? last : next;\n}\n\n/** A value written with the segment's leading zeros: 4 → \"04\". */\nexport function padSegment(value: number, spec: SegmentSpec): string {\n return String(value).padStart(spec.length, '0');\n}\n","import { css, html, nothing, type PropertyValues, type TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { KtElement } from './kt-element.js';\nimport { emit, uniqueId } from './events.js';\nimport {\n attachFormInternals,\n setFormValue,\n setValidity,\n type UsableInternals,\n} from './form-control.js';\nimport { resolveLocale } from './locale.js';\nimport { padSegment, stepSegment, typeDigit, type SegmentSpec } from './segments.js';\n\n/** One part of a segmented field: a number typed digit by digit, or one of a few words. */\nexport interface Segment {\n readonly key: string;\n readonly label: string;\n /** What the segment shows while empty: \"dd\", \"--\". */\n readonly placeholder: string;\n /** A number, typed and stepped within these bounds… */\n readonly spec?: SegmentSpec;\n /** …stepped by this much with the arrows… */\n readonly step?: number;\n /** …or one of these words, picked by its first letter: AM, PM. */\n readonly choices?: readonly string[];\n}\n\n/** What a segmented field is made of: segments, and the characters between them. */\nexport type FieldItem = Segment | { readonly literal: string };\n\nconst isSegment = (item: FieldItem): item is Segment => 'key' in item;\n\n/** Characters that move on to the next segment, as a typist expects. */\nconst SEPARATORS = new Set(['/', '.', '-', ':', ',', ' ']);\n\nexport type KtSegmentedFieldSize = 'small' | 'medium' | 'large';\n\n/**\n * The common ground of `<kt-date-input>` and `<kt-time-input>`: a field of\n * segments — a day, a month, an hour — each its own spinbutton, typed digit by\n * digit, stepped with the arrows, the focus moving on as each fills.\n *\n * Each segment is `contenteditable`, so a phone shows its number pad, but the\n * text never changes behind the field's back: every keystroke and every input\n * event is turned into the segment's new value here. A subclass says what its\n * segments are, how they read into a value, and how a value fills them.\n */\nexport abstract class KtSegmentedField extends KtElement {\n static readonly formAssociated = true;\n\n static override styles = [\n KtElement.styles,\n css`\n :host {\n display: block;\n --field-height: var(--button-height);\n }\n :host([size='small']) {\n --field-height: var(--button-height-small);\n }\n :host([size='large']) {\n --field-height: var(--button-height-large);\n }\n\n /* The field as kt-input draws one: a fill with a resting hairline, an\n outline on hover and focus that never reflows it. */\n .field {\n display: flex;\n align-items: center;\n gap: var(--gap-element);\n height: var(--field-height);\n /* Less the segment's own padding, so the digits line up with a\n kt-input's text. */\n padding: 0 10px 0 calc(var(--button-padding-x) - 2px);\n color: var(--text-body);\n font: var(--font-input);\n background-color: var(--surface-field);\n border: var(--border-width) solid var(--border-field);\n border-radius: var(--radius-input);\n outline: var(--outline-width) solid transparent;\n cursor: text;\n transition: outline-color var(--duration-instant);\n }\n .field:hover {\n outline: var(--outline-width) solid var(--color-text-700);\n }\n .field:focus-within,\n .field.open {\n outline: var(--outline-width) solid var(--color-primary-base);\n }\n .field.error,\n .field.error:hover,\n .field.error:focus-within {\n outline: var(--outline-width) solid var(--color-danger-base);\n }\n .field.disabled {\n color: var(--text-disabled);\n background-color: var(--color-dark-14);\n cursor: not-allowed;\n }\n\n .segments {\n display: flex;\n flex: 1;\n align-items: center;\n min-width: 0;\n font-variant-numeric: tabular-nums;\n }\n\n /* A segment takes a tint while it has focus, so it is plain which part\n a digit will go into; the caret itself would only flicker. */\n .segment {\n padding: 1px 2px;\n border-radius: calc(4px * var(--radius-scale, 1));\n outline: none;\n caret-color: transparent;\n white-space: nowrap;\n }\n .segment.empty {\n color: var(--text-muted);\n }\n .segment:focus {\n color: var(--color-primary-text);\n background: var(--color-primary-soft);\n }\n .error .segment:not(.empty) {\n color: var(--color-danger-text);\n }\n .literal {\n padding: 0 1px;\n color: var(--text-muted);\n }\n\n .error-icon {\n flex: none;\n color: var(--color-danger-text);\n }\n `,\n ];\n\n protected internals: UsableInternals | null = null;\n private defaultValue: string | null = null;\n /** Set while the field writes its own value, which should not refill it. */\n private committing = false;\n protected readonly errorId = uniqueId('kt-segmented-error');\n\n /** What each segment holds, by key: digits, or the index of a choice. */\n @state() protected texts: Record<string, string> = {};\n /** Why the segments do not make a value, when they are all filled. */\n @state() protected typedError = '';\n @state() private focusedKey: string | null = null;\n /** Disabled by an enclosing `<fieldset>`, which leaves `disabled` alone. */\n @state() private formDisabled = false;\n\n @property({ type: String, reflect: true })\n value: string | null = null;\n\n @property({ type: String })\n name = '';\n\n /** Accessible name of the field, when no `<kt-label-input>` wraps it. */\n @property({ type: String })\n label = '';\n\n /** BCP 47 locale for the order and names of the segments. Empty: page, then browser. */\n @property({ type: String })\n locale = '';\n\n @property({ type: String })\n min = '';\n\n @property({ type: String })\n max = '';\n\n @property({ type: String, reflect: true })\n size: KtSegmentedFieldSize = 'medium';\n\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n @property({ type: Boolean, reflect: true })\n required = false;\n\n /** Error message. A non-empty value puts the field in its error state. */\n @property({ type: String, reflect: true })\n error = '';\n\n // --- What a subclass says ---\n\n /** The segments, in order, with what goes between them. */\n protected abstract items(): FieldItem[];\n /** What each segment holds for a value. */\n protected abstract fill(value: string | null): Record<string, string>;\n /** The value the segments make — null until they all hold something — and why not, if not. */\n protected abstract read(texts: Record<string, string>): { value: string | null; error: string };\n /** The message of a required field left empty. */\n protected abstract requiredMessage(): string;\n /** Anything after the segments: a calendar button. */\n protected renderAfter(): TemplateResult | typeof nothing {\n return nothing;\n }\n /**\n * What a segment left half-typed becomes when the focus leaves it — a\n * two-digit year written out — or null to leave it as typed.\n */\n protected completeOnBlur(_segment: Segment, _text: string): string | null {\n return null;\n }\n /** Whether a popup belonging to the field is open, for the field's outline. */\n protected get expanded(): boolean {\n return false;\n }\n\n // --- Lifecycle ---\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.internals ??= attachFormInternals(this);\n this.defaultValue = this.value;\n this.texts = this.fill(this.value);\n }\n\n override willUpdate(changed: PropertyValues<this>): void {\n // A value set from outside fills the segments; one the field wrote does not.\n if ((changed.has('value') || changed.has('locale')) && !this.committing) {\n this.texts = this.fill(this.value);\n this.typedError = '';\n }\n this.committing = false;\n\n if (\n changed.has('value') ||\n changed.has('required') ||\n changed.has('error') ||\n changed.has('typedError' as keyof KtSegmentedField) ||\n this.stringsChanged(changed)\n ) {\n setFormValue(this.internals, this.value);\n const missing = this.required && this.value === null && !this.typedError;\n setValidity(\n this.internals,\n {\n valueMissing: missing,\n badInput: Boolean(this.typedError),\n customError: Boolean(this.error),\n },\n this.error || this.typedError || (missing ? this.requiredMessage() : ''),\n );\n }\n }\n\n formResetCallback(): void {\n this.value = this.defaultValue;\n this.texts = this.fill(this.value);\n this.typedError = '';\n }\n\n /**\n * Called by the platform when the control's disabled state changes — its\n * own `disabled`, or an ancestor `<fieldset disabled>` it cannot see.\n */\n formDisabledCallback(disabled: boolean): void {\n this.formDisabled = disabled;\n }\n\n /** Called by the platform when the browser restores a session. */\n formStateRestoreCallback(state: string | null): void {\n this.value = state;\n }\n\n /** Focuses the first segment. */\n override focus(options?: FocusOptions): void {\n this.segmentElements()[0]?.focus(options);\n }\n\n protected get inactive(): boolean {\n return this.disabled || this.formDisabled;\n }\n\n protected get resolvedLocale(): string {\n return resolveLocale(this.locale);\n }\n\n // --- Values ---\n\n /** Sets a value chosen outside the segments — a day picked in a calendar. */\n protected commitValue(value: string | null): void {\n this.texts = this.fill(value);\n this.typedError = '';\n this.apply(value);\n }\n\n private setText(key: string, text: string): void {\n this.texts = { ...this.texts, [key]: text };\n const { value, error } = this.read(this.texts);\n this.typedError = error;\n this.apply(value);\n }\n\n private apply(value: string | null): void {\n if (value === this.value) return;\n this.committing = true;\n this.value = value;\n emit(this, 'kt-change', { value });\n }\n\n // --- Keyboard ---\n\n private segmentElements(): HTMLElement[] {\n return [...(this.shadowRoot?.querySelectorAll<HTMLElement>('[role=\"spinbutton\"]') ?? [])];\n }\n\n private focusSegment(index: number): void {\n this.segmentElements()[index]?.focus();\n }\n\n /** One key's effect on a segment; true when the key was the field's to handle. */\n private applyKey(key: string, segment: Segment, index: number): boolean {\n const text = this.texts[segment.key] ?? '';\n\n if (/^\\d$/.test(key) && segment.spec) {\n const typed = typeDigit(text, key, segment.spec);\n this.setText(segment.key, typed.text);\n if (typed.advance) this.focusSegment(index + 1);\n return true;\n }\n if (segment.choices && /^\\p{L}$/u.test(key)) {\n const pick = segment.choices.findIndex((choice) =>\n choice.toLowerCase().startsWith(key.toLowerCase()),\n );\n if (pick >= 0) {\n this.setText(segment.key, String(pick));\n this.focusSegment(index + 1);\n }\n return true;\n }\n\n switch (key) {\n case 'ArrowUp':\n case 'ArrowDown': {\n const by = key === 'ArrowUp' ? 1 : -1;\n if (segment.spec) {\n const current = text === '' ? null : Number(text);\n const next = stepSegment(current, by * (segment.step ?? 1), segment.spec);\n this.setText(segment.key, padSegment(next, segment.spec));\n } else if (segment.choices) {\n const count = segment.choices.length;\n const current = text === '' ? (by > 0 ? -1 : 0) : Number(text);\n this.setText(segment.key, String((current + by + count) % count));\n }\n return true;\n }\n case 'Home':\n case 'End':\n if (segment.spec) {\n const bound = key === 'Home' ? segment.spec.min : segment.spec.max;\n this.setText(segment.key, padSegment(bound, segment.spec));\n }\n return true;\n case 'ArrowLeft':\n this.focusSegment(index - 1);\n return true;\n case 'ArrowRight':\n this.focusSegment(index + 1);\n return true;\n case 'Backspace':\n case 'Delete':\n // Erase a digit at a time; once empty, Backspace goes back a segment.\n if (text) this.setText(segment.key, segment.choices ? '' : text.slice(0, -1));\n else if (key === 'Backspace') this.focusSegment(index - 1);\n return true;\n default:\n if (SEPARATORS.has(key)) {\n if (text) this.focusSegment(index + 1);\n return true;\n }\n // Any other character would land in the contenteditable as text.\n return key.length === 1;\n }\n }\n\n private onKeyDown(event: KeyboardEvent, segment: Segment, index: number): void {\n if (this.inactive || event.metaKey || event.ctrlKey || event.altKey) return;\n if (this.applyKey(event.key, segment, index)) event.preventDefault();\n }\n\n /**\n * What a phone's keyboard sends: input events rather than key presses.\n * The text is never left to the browser; each character goes through the\n * same path as a key.\n */\n private onBeforeInput(event: InputEvent, segment: Segment, index: number): void {\n event.preventDefault();\n if (this.inactive) return;\n if (event.inputType.startsWith('delete')) {\n this.applyKey('Backspace', segment, index);\n return;\n }\n for (const character of event.data ?? '') this.applyKey(character, segment, index);\n }\n\n /** A click on the field's padding or separators lands in its first empty segment. */\n private onFieldClick(event: MouseEvent): void {\n if (this.inactive) return;\n if ((event.target as HTMLElement).closest('[role=\"spinbutton\"], button, kt-tooltip')) return;\n const segments = this.items().filter(isSegment);\n const empty = segments.findIndex((segment) => !this.texts[segment.key]);\n this.focusSegment(empty >= 0 ? empty : segments.length - 1);\n }\n\n // --- Rendering ---\n\n private renderSegment(segment: Segment, index: number): TemplateResult {\n const text = this.texts[segment.key] ?? '';\n const focused = this.focusedKey === segment.key;\n let shown = segment.placeholder;\n let now: number | typeof nothing = nothing;\n if (text !== '') {\n now = Number(text);\n if (segment.choices) shown = segment.choices[Number(text)] ?? '';\n else if (segment.spec) shown = focused ? text : padSegment(Number(text), segment.spec);\n }\n\n return html`<span\n part=\"segment\"\n role=\"spinbutton\"\n class=${classMap({ segment: true, empty: text === '' })}\n data-key=${segment.key}\n tabindex=${this.inactive ? -1 : 0}\n contenteditable=${this.inactive ? 'false' : 'true'}\n inputmode=${segment.spec ? 'numeric' : 'text'}\n spellcheck=\"false\"\n autocorrect=\"off\"\n enterkeyhint=\"next\"\n aria-label=${segment.label}\n aria-valuemin=${segment.spec ? segment.spec.min : nothing}\n aria-valuemax=${segment.spec ? segment.spec.max : nothing}\n aria-valuenow=${now}\n aria-valuetext=${text === '' ? segment.placeholder : shown}\n aria-disabled=${this.inactive ? 'true' : nothing}\n @keydown=${(event: KeyboardEvent) => this.onKeyDown(event, segment, index)}\n @beforeinput=${(event: InputEvent) => this.onBeforeInput(event, segment, index)}\n @focus=${() => {\n this.focusedKey = segment.key;\n }}\n @blur=${() => {\n this.focusedKey = null;\n const completed = this.completeOnBlur(segment, this.texts[segment.key] ?? '');\n if (completed !== null && completed !== this.texts[segment.key]) {\n this.setText(segment.key, completed);\n }\n }}\n >${shown}</span\n >`;\n }\n\n override render(): TemplateResult {\n const error = this.error || this.typedError;\n let index = -1;\n\n return html`${\n error ? html`<span id=${this.errorId} class=\"visually-hidden\">${error}</span>` : nothing\n }\n <div\n part=\"field\"\n class=${classMap({\n field: true,\n error: Boolean(error),\n disabled: this.inactive,\n open: this.expanded,\n })}\n role=\"group\"\n aria-label=${this.label || nothing}\n aria-invalid=${error ? 'true' : nothing}\n aria-describedby=${error ? this.errorId : nothing}\n @click=${this.onFieldClick}\n >\n <div class=\"segments\">\n ${this.items().map((item) => {\n if (!isSegment(item)) {\n return html`<span class=\"literal\" aria-hidden=\"true\">${item.literal}</span>`;\n }\n index += 1;\n return this.renderSegment(item, index);\n })}\n </div>\n ${\n error\n ? html`<kt-tooltip class=\"error-icon\" text=${error} ?open=${this.focusedKey !== null}>\n <kt-icon name=\"circle-alert\" size=\"18\"></kt-icon>\n </kt-tooltip>`\n : nothing\n }\n ${this.renderAfter()}\n </div>`;\n }\n}\n"],"names":["typeDigit","current","digit","spec","text","advance","stepSegment","value","by","step","first","last","offset","next","padSegment","isSegment","item","SEPARATORS","KtSegmentedField","KtElement","uniqueId","css","nothing","_segment","_text","attachFormInternals","changed","setFormValue","missing","setValidity","disabled","state","options","resolveLocale","key","error","emit","index","segment","typed","pick","choice","count","bound","event","character","segments","empty","focused","shown","now","html","classMap","completed","__decorateClass","property"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"segmented-field-T_Zo6lc1.js","sources":["../../src/internal/segments.ts","../../src/internal/segmented-field.ts"],"sourcesContent":["/**\n * The arithmetic of a segmented field — a day, a month, a year, an hour —\n * each typed digit by digit and stepped with the arrows. Pure, so the two\n * fields that share it, `<kt-date-input>` and `<kt-time-input>`, agree on\n * every key.\n */\n\nexport interface SegmentSpec {\n readonly min: number;\n readonly max: number;\n /** Digits when full: 2 for a day, 4 for a year. */\n readonly length: number;\n}\n\n/**\n * The segment's text once `digit` is typed, and whether the focus should move\n * on. It moves on when the segment is full, or as soon as no further digit\n * could follow — a day starting with 4 is the 4th. A digit that would\n * overshoot the maximum, or one typed into a full segment, starts it over.\n */\nexport function typeDigit(\n current: string,\n digit: string,\n spec: SegmentSpec,\n): { text: string; advance: boolean } {\n let text = current.length >= spec.length ? digit : current + digit;\n if (Number(text) > spec.max) text = digit;\n const advance = text.length >= spec.length || Number(text) * 10 > spec.max;\n return { text, advance };\n}\n\n/**\n * The value an arrow press lands on: the next multiple of the step — counted\n * from the minimum — in the direction of `by`, wrapping past either end. An\n * empty segment starts from its first value going up, its last going down.\n */\nexport function stepSegment(value: number | null, by: number, spec: SegmentSpec): number {\n const step = Math.abs(by) || 1;\n const first = spec.min;\n const last = spec.min + Math.floor((spec.max - spec.min) / step) * step;\n if (value === null) return by > 0 ? first : last;\n\n const offset = value - spec.min;\n if (by > 0) {\n const next = spec.min + (Math.floor(offset / step) + 1) * step;\n return next > spec.max ? first : next;\n }\n const next = offset % step === 0 ? value - step : spec.min + Math.floor(offset / step) * step;\n return next < spec.min ? last : next;\n}\n\n/** A value written with the segment's leading zeros: 4 → \"04\". */\nexport function padSegment(value: number, spec: SegmentSpec): string {\n return String(value).padStart(spec.length, '0');\n}\n","import { css, html, nothing, type PropertyValues, type TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { KtElement } from './kt-element.js';\nimport { emit, uniqueId } from './events.js';\nimport {\n attachFormInternals,\n setFormValue,\n setValidity,\n type UsableInternals,\n} from './form-control.js';\nimport { resolveLocale } from './locale.js';\nimport { padSegment, stepSegment, typeDigit, type SegmentSpec } from './segments.js';\n\n/** One part of a segmented field: a number typed digit by digit, or one of a few words. */\nexport interface Segment {\n readonly key: string;\n readonly label: string;\n /** What the segment shows while empty: \"dd\", \"--\". */\n readonly placeholder: string;\n /** A number, typed and stepped within these bounds… */\n readonly spec?: SegmentSpec;\n /** …stepped by this much with the arrows… */\n readonly step?: number;\n /** …or one of these words, picked by its first letter: AM, PM. */\n readonly choices?: readonly string[];\n}\n\n/** What a segmented field is made of: segments, and the characters between them. */\nexport type FieldItem = Segment | { readonly literal: string };\n\nconst isSegment = (item: FieldItem): item is Segment => 'key' in item;\n\n/** Characters that move on to the next segment, as a typist expects. */\nconst SEPARATORS = new Set(['/', '.', '-', ':', ',', ' ']);\n\nexport type KtSegmentedFieldSize = 'small' | 'medium' | 'large';\n\n/**\n * The common ground of `<kt-date-input>` and `<kt-time-input>`: a field of\n * segments — a day, a month, an hour — each its own spinbutton, typed digit by\n * digit, stepped with the arrows, the focus moving on as each fills.\n *\n * Each segment is `contenteditable`, so a phone shows its number pad, but the\n * text never changes behind the field's back: every keystroke and every input\n * event is turned into the segment's new value here. A subclass says what its\n * segments are, how they read into a value, and how a value fills them.\n */\nexport abstract class KtSegmentedField extends KtElement {\n static readonly formAssociated = true;\n\n static override styles = [\n KtElement.styles,\n css`\n :host {\n display: block;\n --field-height: var(--button-height);\n }\n :host([size='small']) {\n --field-height: var(--button-height-small);\n }\n :host([size='large']) {\n --field-height: var(--button-height-large);\n }\n\n /* The field as kt-input draws one: a fill with a resting hairline, an\n outline on hover and focus that never reflows it. */\n .field {\n display: flex;\n align-items: center;\n gap: var(--gap-element);\n height: var(--field-height);\n /* Less the segment's own padding, so the digits line up with a\n kt-input's text. */\n padding: 0 10px 0 calc(var(--button-padding-x) - 2px);\n color: var(--text-body);\n font: var(--font-input);\n background-color: var(--surface-field);\n border: var(--border-width) solid var(--border-field);\n border-radius: var(--radius-input);\n outline: var(--outline-width) solid transparent;\n cursor: text;\n transition: outline-color var(--duration-instant);\n }\n .field:hover {\n outline: var(--outline-width) solid var(--color-text-700);\n }\n .field:focus-within,\n .field.open {\n outline: var(--outline-width) solid var(--color-primary-base);\n }\n .field.error,\n .field.error:hover,\n .field.error:focus-within {\n outline: var(--outline-width) solid var(--color-danger-base);\n }\n .field.disabled {\n color: var(--text-disabled);\n background-color: var(--color-dark-14);\n cursor: not-allowed;\n }\n\n .segments {\n display: flex;\n flex: 1;\n align-items: center;\n min-width: 0;\n font-variant-numeric: tabular-nums;\n }\n\n /* A segment takes a tint while it has focus, so it is plain which part\n a digit will go into; the caret itself would only flicker. */\n .segment {\n padding: 1px 2px;\n border-radius: calc(4px * var(--radius-scale, 1));\n outline: none;\n caret-color: transparent;\n white-space: nowrap;\n }\n .segment.empty {\n color: var(--text-muted);\n }\n .segment:focus {\n color: var(--color-primary-text);\n background: var(--color-primary-soft);\n }\n .error .segment:not(.empty) {\n color: var(--color-danger-text);\n }\n .literal {\n padding: 0 1px;\n color: var(--text-muted);\n }\n\n .error-icon {\n flex: none;\n color: var(--color-danger-text);\n }\n `,\n ];\n\n protected internals: UsableInternals | null = null;\n private defaultValue: string | null = null;\n /** Set while the field writes its own value, which should not refill it. */\n private committing = false;\n protected readonly errorId = uniqueId('kt-segmented-error');\n\n /** What each segment holds, by key: digits, or the index of a choice. */\n @state() protected texts: Record<string, string> = {};\n /** Why the segments do not make a value, when they are all filled. */\n @state() protected typedError = '';\n @state() private focusedKey: string | null = null;\n /** Disabled by an enclosing `<fieldset>`, which leaves `disabled` alone. */\n @state() private formDisabled = false;\n\n @property({ type: String, reflect: true })\n value: string | null = null;\n\n @property({ type: String })\n name = '';\n\n /** Accessible name of the field, when no `<kt-label-input>` wraps it. */\n @property({ type: String })\n label = '';\n\n /** BCP 47 locale for the order and names of the segments. Empty: page, then browser. */\n @property({ type: String })\n locale = '';\n\n @property({ type: String })\n min = '';\n\n @property({ type: String })\n max = '';\n\n @property({ type: String, reflect: true })\n size: KtSegmentedFieldSize = 'medium';\n\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n @property({ type: Boolean, reflect: true })\n required = false;\n\n /** Error message. A non-empty value puts the field in its error state. */\n @property({ type: String, reflect: true })\n error = '';\n\n // --- What a subclass says ---\n\n /** The segments, in order, with what goes between them. */\n protected abstract items(): FieldItem[];\n /** What each segment holds for a value. */\n protected abstract fill(value: string | null): Record<string, string>;\n /** The value the segments make — null until they all hold something — and why not, if not. */\n protected abstract read(texts: Record<string, string>): { value: string | null; error: string };\n /** The message of a required field left empty. */\n protected abstract requiredMessage(): string;\n /** Anything after the segments: a calendar button. */\n protected renderAfter(): TemplateResult | typeof nothing {\n return nothing;\n }\n /**\n * What a segment left half-typed becomes when the focus leaves it — a\n * two-digit year written out — or null to leave it as typed.\n */\n protected completeOnBlur(_segment: Segment, _text: string): string | null {\n return null;\n }\n /** Whether a popup belonging to the field is open, for the field's outline. */\n protected get expanded(): boolean {\n return false;\n }\n\n // --- Lifecycle ---\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.internals ??= attachFormInternals(this);\n this.defaultValue = this.value;\n this.texts = this.fill(this.value);\n }\n\n override willUpdate(changed: PropertyValues<this>): void {\n // A value set from outside fills the segments; one the field wrote does not.\n if ((changed.has('value') || changed.has('locale')) && !this.committing) {\n this.texts = this.fill(this.value);\n this.typedError = '';\n }\n this.committing = false;\n\n if (\n changed.has('value') ||\n changed.has('required') ||\n changed.has('error') ||\n changed.has('typedError' as keyof KtSegmentedField) ||\n this.stringsChanged(changed)\n ) {\n setFormValue(this.internals, this.value);\n const missing = this.required && this.value === null && !this.typedError;\n setValidity(\n this.internals,\n {\n valueMissing: missing,\n badInput: Boolean(this.typedError),\n customError: Boolean(this.error),\n },\n this.error || this.typedError || (missing ? this.requiredMessage() : ''),\n );\n }\n }\n\n formResetCallback(): void {\n this.value = this.defaultValue;\n this.texts = this.fill(this.value);\n this.typedError = '';\n }\n\n /**\n * Called by the platform when the control's disabled state changes — its\n * own `disabled`, or an ancestor `<fieldset disabled>` it cannot see.\n */\n formDisabledCallback(disabled: boolean): void {\n this.formDisabled = disabled;\n }\n\n /** Called by the platform when the browser restores a session. */\n formStateRestoreCallback(state: string | null): void {\n this.value = state;\n }\n\n /** Focuses the first segment. */\n override focus(options?: FocusOptions): void {\n this.segmentElements()[0]?.focus(options);\n }\n\n protected get inactive(): boolean {\n return this.disabled || this.formDisabled;\n }\n\n protected get resolvedLocale(): string {\n return resolveLocale(this.locale);\n }\n\n // --- Values ---\n\n /** Sets a value chosen outside the segments — a day picked in a calendar. */\n protected commitValue(value: string | null): void {\n this.texts = this.fill(value);\n this.typedError = '';\n this.apply(value);\n }\n\n private setText(key: string, text: string): void {\n this.texts = { ...this.texts, [key]: text };\n const { value, error } = this.read(this.texts);\n this.typedError = error;\n this.apply(value);\n }\n\n private apply(value: string | null): void {\n if (value === this.value) return;\n this.committing = true;\n this.value = value;\n emit(this, 'kt-change', { value });\n }\n\n // --- Keyboard ---\n\n private segmentElements(): HTMLElement[] {\n return [...(this.shadowRoot?.querySelectorAll<HTMLElement>('[role=\"spinbutton\"]') ?? [])];\n }\n\n private focusSegment(index: number): void {\n this.segmentElements()[index]?.focus();\n }\n\n /** One key's effect on a segment; true when the key was the field's to handle. */\n private applyKey(key: string, segment: Segment, index: number): boolean {\n const text = this.texts[segment.key] ?? '';\n\n if (/^\\d$/.test(key) && segment.spec) {\n const typed = typeDigit(text, key, segment.spec);\n this.setText(segment.key, typed.text);\n if (typed.advance) this.focusSegment(index + 1);\n return true;\n }\n if (segment.choices && /^\\p{L}$/u.test(key)) {\n const pick = segment.choices.findIndex((choice) =>\n choice.toLowerCase().startsWith(key.toLowerCase()),\n );\n if (pick >= 0) {\n this.setText(segment.key, String(pick));\n this.focusSegment(index + 1);\n }\n return true;\n }\n\n switch (key) {\n case 'ArrowUp':\n case 'ArrowDown': {\n const by = key === 'ArrowUp' ? 1 : -1;\n if (segment.spec) {\n const current = text === '' ? null : Number(text);\n const next = stepSegment(current, by * (segment.step ?? 1), segment.spec);\n this.setText(segment.key, padSegment(next, segment.spec));\n } else if (segment.choices) {\n const count = segment.choices.length;\n const current = text === '' ? (by > 0 ? -1 : 0) : Number(text);\n this.setText(segment.key, String((current + by + count) % count));\n }\n return true;\n }\n case 'Home':\n case 'End':\n if (segment.spec) {\n const bound = key === 'Home' ? segment.spec.min : segment.spec.max;\n this.setText(segment.key, padSegment(bound, segment.spec));\n }\n return true;\n case 'ArrowLeft':\n this.focusSegment(index - 1);\n return true;\n case 'ArrowRight':\n this.focusSegment(index + 1);\n return true;\n case 'Backspace':\n case 'Delete':\n // Erase a digit at a time; once empty, Backspace goes back a segment.\n if (text) this.setText(segment.key, segment.choices ? '' : text.slice(0, -1));\n else if (key === 'Backspace') this.focusSegment(index - 1);\n return true;\n default:\n if (SEPARATORS.has(key)) {\n if (text) this.focusSegment(index + 1);\n return true;\n }\n // Any other character would land in the contenteditable as text.\n return key.length === 1;\n }\n }\n\n private onKeyDown(event: KeyboardEvent, segment: Segment, index: number): void {\n if (this.inactive || event.metaKey || event.ctrlKey || event.altKey) return;\n if (this.applyKey(event.key, segment, index)) event.preventDefault();\n }\n\n /**\n * What a phone's keyboard sends: input events rather than key presses.\n * The text is never left to the browser; each character goes through the\n * same path as a key.\n */\n private onBeforeInput(event: InputEvent, segment: Segment, index: number): void {\n event.preventDefault();\n if (this.inactive) return;\n if (event.inputType.startsWith('delete')) {\n this.applyKey('Backspace', segment, index);\n return;\n }\n for (const character of event.data ?? '') this.applyKey(character, segment, index);\n }\n\n /** A click on the field's padding or separators lands in its first empty segment. */\n private onFieldClick(event: MouseEvent): void {\n if (this.inactive) return;\n if ((event.target as HTMLElement).closest('[role=\"spinbutton\"], button, kt-tooltip')) return;\n const segments = this.items().filter(isSegment);\n const empty = segments.findIndex((segment) => !this.texts[segment.key]);\n this.focusSegment(empty >= 0 ? empty : segments.length - 1);\n }\n\n // --- Rendering ---\n\n private renderSegment(segment: Segment, index: number): TemplateResult {\n const text = this.texts[segment.key] ?? '';\n const focused = this.focusedKey === segment.key;\n let shown = segment.placeholder;\n let now: number | typeof nothing = nothing;\n if (text !== '') {\n now = Number(text);\n if (segment.choices) shown = segment.choices[Number(text)] ?? '';\n else if (segment.spec) shown = focused ? text : padSegment(Number(text), segment.spec);\n }\n\n return html`<span\n part=\"segment\"\n role=\"spinbutton\"\n class=${classMap({ segment: true, empty: text === '' })}\n data-key=${segment.key}\n tabindex=${this.inactive ? -1 : 0}\n contenteditable=${this.inactive ? 'false' : 'true'}\n inputmode=${segment.spec ? 'numeric' : 'text'}\n spellcheck=\"false\"\n autocorrect=\"off\"\n enterkeyhint=\"next\"\n aria-label=${segment.label}\n aria-valuemin=${segment.spec ? segment.spec.min : nothing}\n aria-valuemax=${segment.spec ? segment.spec.max : nothing}\n aria-valuenow=${now}\n aria-valuetext=${text === '' ? segment.placeholder : shown}\n aria-disabled=${this.inactive ? 'true' : nothing}\n @keydown=${(event: KeyboardEvent) => this.onKeyDown(event, segment, index)}\n @beforeinput=${(event: InputEvent) => this.onBeforeInput(event, segment, index)}\n @focus=${() => {\n this.focusedKey = segment.key;\n }}\n @blur=${() => {\n this.focusedKey = null;\n const completed = this.completeOnBlur(segment, this.texts[segment.key] ?? '');\n if (completed !== null && completed !== this.texts[segment.key]) {\n this.setText(segment.key, completed);\n }\n }}\n >${shown}</span\n >`;\n }\n\n override render(): TemplateResult {\n const error = this.error || this.typedError;\n let index = -1;\n\n return html`${\n error ? html`<span id=${this.errorId} class=\"visually-hidden\">${error}</span>` : nothing\n }\n <div\n part=\"field\"\n class=${classMap({\n field: true,\n error: Boolean(error),\n disabled: this.inactive,\n open: this.expanded,\n })}\n role=\"group\"\n aria-label=${this.label || nothing}\n aria-invalid=${error ? 'true' : nothing}\n aria-describedby=${error ? this.errorId : nothing}\n @click=${this.onFieldClick}\n >\n <div class=\"segments\">\n ${this.items().map((item) => {\n if (!isSegment(item)) {\n return html`<span class=\"literal\" aria-hidden=\"true\">${item.literal}</span>`;\n }\n index += 1;\n return this.renderSegment(item, index);\n })}\n </div>\n ${\n error\n ? html`<kt-tooltip class=\"error-icon\" text=${error} ?open=${this.focusedKey !== null}>\n <kt-icon name=\"circle-alert\" size=\"18\"></kt-icon>\n </kt-tooltip>`\n : nothing\n }\n ${this.renderAfter()}\n </div>`;\n }\n}\n"],"names":["typeDigit","current","digit","spec","text","advance","stepSegment","value","by","step","first","last","offset","next","padSegment","isSegment","item","SEPARATORS","KtSegmentedField","KtElement","uniqueId","css","nothing","_segment","_text","attachFormInternals","changed","setFormValue","missing","setValidity","disabled","state","options","resolveLocale","key","error","emit","index","segment","typed","pick","choice","count","bound","event","character","segments","empty","focused","shown","now","html","classMap","completed","__decorateClass","property"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as w, p as x, d as $ } from "./kt-calendar-
|
|
1
|
+
import { c as w, p as x, d as $ } from "./kt-calendar-SK8eapuy.js";
|
|
2
2
|
import { d } from "./locale-C5xLkGx5.js";
|
|
3
3
|
function y(t) {
|
|
4
4
|
return t.normalize("NFD").replace(/\p{Diacritic}/gu, "").replace(/\.$/, "").toLowerCase();
|
|
@@ -100,4 +100,4 @@ export {
|
|
|
100
100
|
B as p,
|
|
101
101
|
L as t
|
|
102
102
|
};
|
|
103
|
-
//# sourceMappingURL=typed-date-
|
|
103
|
+
//# sourceMappingURL=typed-date-CaU0kXoY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-date-Z3vcUJbb.js","sources":["../../src/internal/typed-date.ts"],"sourcesContent":["/**\n * Reading a date someone typed, in their language.\n *\n * A field that only opens a calendar makes a date ten years away, or one\n * already on paper, a chore. Typed, it is a second's work — if the field reads\n * it the way the person writes it: day then month in London, month then day in\n * New York, month names in their own words, the year left out when it is this\n * one.\n */\nimport { daysInMonth, parseDate, compareDates, type PlainDate } from './date.js';\nimport { dateFormat } from './locale.js';\n\n/** Lowercase, without accents or a trailing full stop: \"Févr.\" reads \"fevr\". */\nfunction fold(text: string): string {\n return text\n .normalize('NFD')\n .replace(/\\p{Diacritic}/gu, '')\n .replace(/\\.$/, '')\n .toLowerCase();\n}\n\n/**\n * How the language writes a date in numbers: the order of day, month and year,\n * and what goes between them — `25/09/2026`, `09/25/2026`, `25.09.2026`.\n */\nexport function dateLayout(locale: string): {\n order: ('day' | 'month' | 'year')[];\n separator: string;\n} {\n const parts = dateFormat(locale, {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n }).formatToParts(new Date(2026, 8, 30));\n const separator = parts.find((part) => part.type === 'literal')?.value.trim() || '/';\n return { order: numericOrder(locale), separator };\n}\n\n/** The order the language writes day, month and year in numbers. */\nfunction numericOrder(locale: string): ('day' | 'month' | 'year')[] {\n const parts = dateFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' })\n .formatToParts(new Date(2026, 8, 30))\n .map((part) => part.type)\n .filter((type): type is 'day' | 'month' | 'year' => ['day', 'month', 'year'].includes(type));\n return parts.length === 3 ? parts : ['day', 'month', 'year'];\n}\n\n/** The month a word names in the language, or null when it names none or several. */\nfunction monthNamed(word: string, locale: string): number | null {\n const typed = fold(word);\n if (typed.length < 3) return null;\n\n const matches = new Set<number>();\n for (let month = 1; month <= 12; month += 1) {\n const sample = new Date(2026, month - 1, 1);\n const long = fold(dateFormat(locale, { month: 'long' }).format(sample));\n const short = fold(dateFormat(locale, { month: 'short' }).format(sample));\n if (long.startsWith(typed) || short === typed) matches.add(month);\n }\n return matches.size === 1 ? [...matches][0]! : null;\n}\n\n/** A typed year: two digits are this century, or the last, whichever is nearer. */\nfunction readYear(digits: string, reference: number): number | null {\n if (digits.length === 4) return Number(digits);\n if (digits.length > 2) return null;\n const year = 2000 + Number(digits);\n return year > reference + 20 ? year - 100 : year;\n}\n\nfunction valid(year: number, month: number, day: number): PlainDate | null {\n if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) return null;\n return { year, month, day };\n}\n\n/** The date, and whether a year was typed or borrowed. */\nfunction read(\n text: string,\n locale: string,\n reference: PlainDate,\n): { date: PlainDate; typedYear: boolean } | null {\n const trimmed = text.trim();\n if (!trimmed) return null;\n\n const iso = /^\\d{4}-\\d{1,2}-\\d{1,2}$/.test(trimmed)\n ? parseDate(trimmed.replace(/-(\\d)(?=-|$)/g, '-0$1'))\n : null;\n if (iso) return { date: iso, typedYear: true };\n\n const numbers: string[] = [];\n let named: number | null = null;\n for (const token of trimmed.split(/[\\s/.,-]+/).filter(Boolean)) {\n if (/^\\d+$/.test(token)) {\n numbers.push(token);\n continue;\n }\n const month = monthNamed(token, locale);\n if (month === null || named !== null) return null;\n named = month;\n }\n\n if (named !== null) {\n // \"25 Sept 2026\", \"Sep 25, 2026\", \"3 march\": the day, and maybe a year.\n if (numbers.length === 0 || numbers.length > 2) return null;\n const yearIndex = numbers.findIndex((token) => token.length === 4 || Number(token) > 31);\n const dayToken = numbers.find((_, index) => index !== yearIndex);\n if (dayToken === undefined || (numbers.length === 2 && yearIndex < 0)) return null;\n const year = yearIndex >= 0 ? readYear(numbers[yearIndex]!, reference.year) : reference.year;\n if (year === null) return null;\n const date = valid(year, named, Number(dayToken));\n return date && { date, typedYear: yearIndex >= 0 };\n }\n\n // All numbers, in the language's order; a leading four-digit year reads ISO-wise.\n if (numbers.length < 2 || numbers.length > 3) return null;\n const order =\n numbers[0]!.length === 4\n ? (['year', 'month', 'day'] as const)\n : numberOrderFor(numericOrder(locale), numbers.length);\n const field = (name: 'day' | 'month' | 'year') => numbers[order.indexOf(name)];\n\n const yearToken = field('year');\n const year = yearToken === undefined ? reference.year : readYear(yearToken, reference.year);\n if (year === null) return null;\n const date = valid(year, Number(field('month')), Number(field('day')));\n return date && { date, typedYear: yearToken !== undefined };\n}\n\n/** Two numbers are the day and month, in the language's order, with the year left out. */\nfunction numberOrderFor(\n order: ('day' | 'month' | 'year')[],\n count: number,\n): readonly ('day' | 'month' | 'year')[] {\n return count === 3 ? order : order.filter((part) => part !== 'year');\n}\n\n/** A typed year: two digits are this century or the last, whichever is nearer. */\nexport function typedYear(digits: string, reference: number): number | null {\n return readYear(digits, reference);\n}\n\n/** A date typed in `locale`, or null when it does not read as one. */\nexport function parseTypedDate(\n text: string,\n locale: string,\n reference: PlainDate,\n): PlainDate | null {\n return read(text, locale, reference)?.date ?? null;\n}\n\n/**\n * A period typed as two dates around a dash — \"1/9 – 25/9/2026\" — in order.\n * A start typed without a year borrows the end's, the year before if that\n * would put it after the end: \"20/12 – 5/1/2027\" starts in December 2026.\n */\nexport function parseTypedRange(\n text: string,\n locale: string,\n reference: PlainDate,\n): { start: PlainDate; end: PlainDate } | null {\n const halves = text.split(/\\s*[–—]\\s*|\\s+-\\s+|\\s+(?:to|au|bis|à)\\s+/i);\n if (halves.length !== 2) return null;\n\n const end = read(halves[1]!, locale, reference);\n if (!end) return null;\n const start = read(halves[0]!, locale, { ...reference, year: end.date.year });\n if (!start) return null;\n\n let from = start.date;\n if (!start.typedYear && compareDates(from, end.date) > 0) {\n const earlier = valid(from.year - 1, from.month, from.day);\n if (earlier) from = earlier;\n }\n return compareDates(from, end.date) <= 0\n ? { start: from, end: end.date }\n : { start: end.date, end: from };\n}\n\n/** `reference` written in numbers the way `locale` writes a date — for a hint. */\nexport function exampleDate(locale: string, reference: PlainDate): string {\n return dateFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }).format(\n new Date(reference.year, reference.month - 1, reference.day, 12),\n );\n}\n"],"names":["fold","text","dateLayout","locale","separator","dateFormat","part","numericOrder","parts","type","monthNamed","word","typed","matches","month","sample","long","short","readYear","digits","reference","year","valid","day","daysInMonth","read","trimmed","iso","parseDate","numbers","named","token","yearIndex","dayToken","_","index","date","order","numberOrderFor","field","name","yearToken","count","typedYear","parseTypedDate","parseTypedRange","halves","end","start","from","compareDates","earlier","exampleDate"],"mappings":";;AAaA,SAASA,EAAKC,GAAsB;AAClC,SAAOA,EACJ,UAAU,KAAK,EACf,QAAQ,mBAAmB,EAAE,EAC7B,QAAQ,OAAO,EAAE,EACjB,YAAA;AACL;AAMO,SAASC,EAAWC,GAGzB;AAMA,QAAMC,IALQC,EAAWF,GAAQ;AAAA,IAC/B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,EAAA,CACP,EAAE,cAAc,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,EACd,KAAK,CAACG,MAASA,EAAK,SAAS,SAAS,GAAG,MAAM,KAAA,KAAU;AACjF,SAAO,EAAE,OAAOC,EAAaJ,CAAM,GAAG,WAAAC,EAAA;AACxC;AAGA,SAASG,EAAaJ,GAA8C;AAClE,QAAMK,IAAQH,EAAWF,GAAQ,EAAE,KAAK,WAAW,OAAO,WAAW,MAAM,UAAA,CAAW,EACnF,cAAc,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,EACnC,IAAI,CAACG,MAASA,EAAK,IAAI,EACvB,OAAO,CAACG,MAA2C,CAAC,OAAO,SAAS,MAAM,EAAE,SAASA,CAAI,CAAC;AAC7F,SAAOD,EAAM,WAAW,IAAIA,IAAQ,CAAC,OAAO,SAAS,MAAM;AAC7D;AAGA,SAASE,EAAWC,GAAcR,GAA+B;AAC/D,QAAMS,IAAQZ,EAAKW,CAAI;AACvB,MAAIC,EAAM,SAAS,EAAG,QAAO;AAE7B,QAAMC,wBAAc,IAAA;AACpB,WAASC,IAAQ,GAAGA,KAAS,IAAIA,KAAS,GAAG;AAC3C,UAAMC,IAAS,IAAI,KAAK,MAAMD,IAAQ,GAAG,CAAC,GACpCE,IAAOhB,EAAKK,EAAWF,GAAQ,EAAE,OAAO,QAAQ,EAAE,OAAOY,CAAM,CAAC,GAChEE,IAAQjB,EAAKK,EAAWF,GAAQ,EAAE,OAAO,SAAS,EAAE,OAAOY,CAAM,CAAC;AACxE,KAAIC,EAAK,WAAWJ,CAAK,KAAKK,MAAUL,MAAOC,EAAQ,IAAIC,CAAK;AAAA,EAClE;AACA,SAAOD,EAAQ,SAAS,IAAI,CAAC,GAAGA,CAAO,EAAE,CAAC,IAAK;AACjD;AAGA,SAASK,EAASC,GAAgBC,GAAkC;AAClE,MAAID,EAAO,WAAW,EAAG,QAAO,OAAOA,CAAM;AAC7C,MAAIA,EAAO,SAAS,EAAG,QAAO;AAC9B,QAAME,IAAO,MAAO,OAAOF,CAAM;AACjC,SAAOE,IAAOD,IAAY,KAAKC,IAAO,MAAMA;AAC9C;AAEA,SAASC,EAAMD,GAAcP,GAAeS,GAA+B;AACzE,SAAIT,IAAQ,KAAKA,IAAQ,MAAMS,IAAM,KAAKA,IAAMC,EAAYH,GAAMP,CAAK,IAAU,OAC1E,EAAE,MAAAO,GAAM,OAAAP,GAAO,KAAAS,EAAA;AACxB;AAGA,SAASE,EACPxB,GACAE,GACAiB,GACgD;AAChD,QAAMM,IAAUzB,EAAK,KAAA;AACrB,MAAI,CAACyB,EAAS,QAAO;AAErB,QAAMC,IAAM,0BAA0B,KAAKD,CAAO,IAC9CE,EAAUF,EAAQ,QAAQ,iBAAiB,MAAM,CAAC,IAClD;AACJ,MAAIC,EAAK,QAAO,EAAE,MAAMA,GAAK,WAAW,GAAA;AAExC,QAAME,IAAoB,CAAA;AAC1B,MAAIC,IAAuB;AAC3B,aAAWC,KAASL,EAAQ,MAAM,WAAW,EAAE,OAAO,OAAO,GAAG;AAC9D,QAAI,QAAQ,KAAKK,CAAK,GAAG;AACvB,MAAAF,EAAQ,KAAKE,CAAK;AAClB;AAAA,IACF;AACA,UAAMjB,IAAQJ,EAAWqB,GAAO5B,CAAM;AACtC,QAAIW,MAAU,QAAQgB,MAAU,KAAM,QAAO;AAC7C,IAAAA,IAAQhB;AAAA,EACV;AAEA,MAAIgB,MAAU,MAAM;AAElB,QAAID,EAAQ,WAAW,KAAKA,EAAQ,SAAS,EAAG,QAAO;AACvD,UAAMG,IAAYH,EAAQ,UAAU,CAACE,MAAUA,EAAM,WAAW,KAAK,OAAOA,CAAK,IAAI,EAAE,GACjFE,IAAWJ,EAAQ,KAAK,CAACK,GAAGC,MAAUA,MAAUH,CAAS;AAC/D,QAAIC,MAAa,UAAcJ,EAAQ,WAAW,KAAKG,IAAY,EAAI,QAAO;AAC9E,UAAMX,IAAOW,KAAa,IAAId,EAASW,EAAQG,CAAS,GAAIZ,EAAU,IAAI,IAAIA,EAAU;AACxF,QAAIC,MAAS,KAAM,QAAO;AAC1B,UAAMe,IAAOd,EAAMD,GAAMS,GAAO,OAAOG,CAAQ,CAAC;AAChD,WAAOG,KAAQ,EAAE,MAAAA,GAAM,WAAWJ,KAAa,EAAA;AAAA,EACjD;AAGA,MAAIH,EAAQ,SAAS,KAAKA,EAAQ,SAAS,EAAG,QAAO;AACrD,QAAMQ,IACJR,EAAQ,CAAC,EAAG,WAAW,IAClB,CAAC,QAAQ,SAAS,KAAK,IACxBS,EAAe/B,EAAaJ,CAAM,GAAG0B,EAAQ,MAAM,GACnDU,IAAQ,CAACC,MAAmCX,EAAQQ,EAAM,QAAQG,CAAI,CAAC,GAEvEC,IAAYF,EAAM,MAAM,GACxBlB,IAAOoB,MAAc,SAAYrB,EAAU,OAAOF,EAASuB,GAAWrB,EAAU,IAAI;AAC1F,MAAIC,MAAS,KAAM,QAAO;AAC1B,QAAMe,IAAOd,EAAMD,GAAM,OAAOkB,EAAM,OAAO,CAAC,GAAG,OAAOA,EAAM,KAAK,CAAC,CAAC;AACrE,SAAOH,KAAQ,EAAE,MAAAA,GAAM,WAAWK,MAAc,OAAA;AAClD;AAGA,SAASH,EACPD,GACAK,GACuC;AACvC,SAAOA,MAAU,IAAIL,IAAQA,EAAM,OAAO,CAAC/B,MAASA,MAAS,MAAM;AACrE;AAGO,SAASqC,EAAUxB,GAAgBC,GAAkC;AAC1E,SAAOF,EAASC,GAAQC,CAAS;AACnC;AAGO,SAASwB,EACd3C,GACAE,GACAiB,GACkB;AAClB,SAAOK,EAAKxB,GAAME,GAAQiB,CAAS,GAAG,QAAQ;AAChD;AAOO,SAASyB,EACd5C,GACAE,GACAiB,GAC6C;AAC7C,QAAM0B,IAAS7C,EAAK,MAAM,2CAA2C;AACrE,MAAI6C,EAAO,WAAW,EAAG,QAAO;AAEhC,QAAMC,IAAMtB,EAAKqB,EAAO,CAAC,GAAI3C,GAAQiB,CAAS;AAC9C,MAAI,CAAC2B,EAAK,QAAO;AACjB,QAAMC,IAAQvB,EAAKqB,EAAO,CAAC,GAAI3C,GAAQ,EAAE,GAAGiB,GAAW,MAAM2B,EAAI,KAAK,MAAM;AAC5E,MAAI,CAACC,EAAO,QAAO;AAEnB,MAAIC,IAAOD,EAAM;AACjB,MAAI,CAACA,EAAM,aAAaE,EAAaD,GAAMF,EAAI,IAAI,IAAI,GAAG;AACxD,UAAMI,IAAU7B,EAAM2B,EAAK,OAAO,GAAGA,EAAK,OAAOA,EAAK,GAAG;AACzD,IAAIE,MAASF,IAAOE;AAAA,EACtB;AACA,SAAOD,EAAaD,GAAMF,EAAI,IAAI,KAAK,IACnC,EAAE,OAAOE,GAAM,KAAKF,EAAI,SACxB,EAAE,OAAOA,EAAI,MAAM,KAAKE,EAAA;AAC9B;AAGO,SAASG,EAAYjD,GAAgBiB,GAA8B;AACxE,SAAOf,EAAWF,GAAQ,EAAE,KAAK,WAAW,OAAO,WAAW,MAAM,UAAA,CAAW,EAAE;AAAA,IAC/E,IAAI,KAAKiB,EAAU,MAAMA,EAAU,QAAQ,GAAGA,EAAU,KAAK,EAAE;AAAA,EAAA;AAEnE;"}
|
|
1
|
+
{"version":3,"file":"typed-date-CaU0kXoY.js","sources":["../../src/internal/typed-date.ts"],"sourcesContent":["/**\n * Reading a date someone typed, in their language.\n *\n * A field that only opens a calendar makes a date ten years away, or one\n * already on paper, a chore. Typed, it is a second's work — if the field reads\n * it the way the person writes it: day then month in London, month then day in\n * New York, month names in their own words, the year left out when it is this\n * one.\n */\nimport { daysInMonth, parseDate, compareDates, type PlainDate } from './date.js';\nimport { dateFormat } from './locale.js';\n\n/** Lowercase, without accents or a trailing full stop: \"Févr.\" reads \"fevr\". */\nfunction fold(text: string): string {\n return text\n .normalize('NFD')\n .replace(/\\p{Diacritic}/gu, '')\n .replace(/\\.$/, '')\n .toLowerCase();\n}\n\n/**\n * How the language writes a date in numbers: the order of day, month and year,\n * and what goes between them — `25/09/2026`, `09/25/2026`, `25.09.2026`.\n */\nexport function dateLayout(locale: string): {\n order: ('day' | 'month' | 'year')[];\n separator: string;\n} {\n const parts = dateFormat(locale, {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n }).formatToParts(new Date(2026, 8, 30));\n const separator = parts.find((part) => part.type === 'literal')?.value.trim() || '/';\n return { order: numericOrder(locale), separator };\n}\n\n/** The order the language writes day, month and year in numbers. */\nfunction numericOrder(locale: string): ('day' | 'month' | 'year')[] {\n const parts = dateFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' })\n .formatToParts(new Date(2026, 8, 30))\n .map((part) => part.type)\n .filter((type): type is 'day' | 'month' | 'year' => ['day', 'month', 'year'].includes(type));\n return parts.length === 3 ? parts : ['day', 'month', 'year'];\n}\n\n/** The month a word names in the language, or null when it names none or several. */\nfunction monthNamed(word: string, locale: string): number | null {\n const typed = fold(word);\n if (typed.length < 3) return null;\n\n const matches = new Set<number>();\n for (let month = 1; month <= 12; month += 1) {\n const sample = new Date(2026, month - 1, 1);\n const long = fold(dateFormat(locale, { month: 'long' }).format(sample));\n const short = fold(dateFormat(locale, { month: 'short' }).format(sample));\n if (long.startsWith(typed) || short === typed) matches.add(month);\n }\n return matches.size === 1 ? [...matches][0]! : null;\n}\n\n/** A typed year: two digits are this century, or the last, whichever is nearer. */\nfunction readYear(digits: string, reference: number): number | null {\n if (digits.length === 4) return Number(digits);\n if (digits.length > 2) return null;\n const year = 2000 + Number(digits);\n return year > reference + 20 ? year - 100 : year;\n}\n\nfunction valid(year: number, month: number, day: number): PlainDate | null {\n if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) return null;\n return { year, month, day };\n}\n\n/** The date, and whether a year was typed or borrowed. */\nfunction read(\n text: string,\n locale: string,\n reference: PlainDate,\n): { date: PlainDate; typedYear: boolean } | null {\n const trimmed = text.trim();\n if (!trimmed) return null;\n\n const iso = /^\\d{4}-\\d{1,2}-\\d{1,2}$/.test(trimmed)\n ? parseDate(trimmed.replace(/-(\\d)(?=-|$)/g, '-0$1'))\n : null;\n if (iso) return { date: iso, typedYear: true };\n\n const numbers: string[] = [];\n let named: number | null = null;\n for (const token of trimmed.split(/[\\s/.,-]+/).filter(Boolean)) {\n if (/^\\d+$/.test(token)) {\n numbers.push(token);\n continue;\n }\n const month = monthNamed(token, locale);\n if (month === null || named !== null) return null;\n named = month;\n }\n\n if (named !== null) {\n // \"25 Sept 2026\", \"Sep 25, 2026\", \"3 march\": the day, and maybe a year.\n if (numbers.length === 0 || numbers.length > 2) return null;\n const yearIndex = numbers.findIndex((token) => token.length === 4 || Number(token) > 31);\n const dayToken = numbers.find((_, index) => index !== yearIndex);\n if (dayToken === undefined || (numbers.length === 2 && yearIndex < 0)) return null;\n const year = yearIndex >= 0 ? readYear(numbers[yearIndex]!, reference.year) : reference.year;\n if (year === null) return null;\n const date = valid(year, named, Number(dayToken));\n return date && { date, typedYear: yearIndex >= 0 };\n }\n\n // All numbers, in the language's order; a leading four-digit year reads ISO-wise.\n if (numbers.length < 2 || numbers.length > 3) return null;\n const order =\n numbers[0]!.length === 4\n ? (['year', 'month', 'day'] as const)\n : numberOrderFor(numericOrder(locale), numbers.length);\n const field = (name: 'day' | 'month' | 'year') => numbers[order.indexOf(name)];\n\n const yearToken = field('year');\n const year = yearToken === undefined ? reference.year : readYear(yearToken, reference.year);\n if (year === null) return null;\n const date = valid(year, Number(field('month')), Number(field('day')));\n return date && { date, typedYear: yearToken !== undefined };\n}\n\n/** Two numbers are the day and month, in the language's order, with the year left out. */\nfunction numberOrderFor(\n order: ('day' | 'month' | 'year')[],\n count: number,\n): readonly ('day' | 'month' | 'year')[] {\n return count === 3 ? order : order.filter((part) => part !== 'year');\n}\n\n/** A typed year: two digits are this century or the last, whichever is nearer. */\nexport function typedYear(digits: string, reference: number): number | null {\n return readYear(digits, reference);\n}\n\n/** A date typed in `locale`, or null when it does not read as one. */\nexport function parseTypedDate(\n text: string,\n locale: string,\n reference: PlainDate,\n): PlainDate | null {\n return read(text, locale, reference)?.date ?? null;\n}\n\n/**\n * A period typed as two dates around a dash — \"1/9 – 25/9/2026\" — in order.\n * A start typed without a year borrows the end's, the year before if that\n * would put it after the end: \"20/12 – 5/1/2027\" starts in December 2026.\n */\nexport function parseTypedRange(\n text: string,\n locale: string,\n reference: PlainDate,\n): { start: PlainDate; end: PlainDate } | null {\n const halves = text.split(/\\s*[–—]\\s*|\\s+-\\s+|\\s+(?:to|au|bis|à)\\s+/i);\n if (halves.length !== 2) return null;\n\n const end = read(halves[1]!, locale, reference);\n if (!end) return null;\n const start = read(halves[0]!, locale, { ...reference, year: end.date.year });\n if (!start) return null;\n\n let from = start.date;\n if (!start.typedYear && compareDates(from, end.date) > 0) {\n const earlier = valid(from.year - 1, from.month, from.day);\n if (earlier) from = earlier;\n }\n return compareDates(from, end.date) <= 0\n ? { start: from, end: end.date }\n : { start: end.date, end: from };\n}\n\n/** `reference` written in numbers the way `locale` writes a date — for a hint. */\nexport function exampleDate(locale: string, reference: PlainDate): string {\n return dateFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }).format(\n new Date(reference.year, reference.month - 1, reference.day, 12),\n );\n}\n"],"names":["fold","text","dateLayout","locale","separator","dateFormat","part","numericOrder","parts","type","monthNamed","word","typed","matches","month","sample","long","short","readYear","digits","reference","year","valid","day","daysInMonth","read","trimmed","iso","parseDate","numbers","named","token","yearIndex","dayToken","_","index","date","order","numberOrderFor","field","name","yearToken","count","typedYear","parseTypedDate","parseTypedRange","halves","end","start","from","compareDates","earlier","exampleDate"],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { KtElement } from '#internal/kt-element';
|
|
3
|
+
import '../../core/kt-button/kt-button.js';
|
|
4
|
+
import '../../feedback/kt-alert/kt-alert.js';
|
|
5
|
+
import '../../forms/kt-checkbox/kt-checkbox.js';
|
|
6
|
+
import '../../forms/kt-input/kt-input.js';
|
|
7
|
+
import '../../forms/kt-label-input/kt-label-input.js';
|
|
8
|
+
import '../../forms/kt-otp-input/kt-otp-input.js';
|
|
9
|
+
export type KtAuthMode = 'sign-in' | 'sign-up' | 'forgot' | 'code';
|
|
10
|
+
export interface KtAuthProvider {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
/** What the button says: "Continue with GitHub". */
|
|
13
|
+
readonly label: string;
|
|
14
|
+
/** A Lucide icon name, registered by the page. */
|
|
15
|
+
readonly icon?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Every word the form says, in English until given others. */
|
|
18
|
+
export interface KtAuthTexts {
|
|
19
|
+
readonly signInHeading: string;
|
|
20
|
+
readonly signInLead: string;
|
|
21
|
+
readonly signUpHeading: string;
|
|
22
|
+
readonly signUpLead: string;
|
|
23
|
+
readonly forgotHeading: string;
|
|
24
|
+
readonly forgotLead: string;
|
|
25
|
+
readonly codeHeading: string;
|
|
26
|
+
readonly codeLead: string;
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly email: string;
|
|
29
|
+
readonly password: string;
|
|
30
|
+
readonly code: string;
|
|
31
|
+
readonly remember: string;
|
|
32
|
+
readonly forgotLink: string;
|
|
33
|
+
readonly signIn: string;
|
|
34
|
+
readonly signUp: string;
|
|
35
|
+
readonly sendLink: string;
|
|
36
|
+
readonly verify: string;
|
|
37
|
+
readonly toSignUp: string;
|
|
38
|
+
readonly toSignUpLink: string;
|
|
39
|
+
readonly toSignIn: string;
|
|
40
|
+
readonly toSignInLink: string;
|
|
41
|
+
readonly or: string;
|
|
42
|
+
readonly sentHeading: string;
|
|
43
|
+
readonly sentLead: string;
|
|
44
|
+
readonly nameMissing: string;
|
|
45
|
+
readonly emailInvalid: string;
|
|
46
|
+
readonly passwordMissing: string;
|
|
47
|
+
readonly passwordShort: (least: number) => string;
|
|
48
|
+
readonly codeMissing: string;
|
|
49
|
+
readonly failed: string;
|
|
50
|
+
}
|
|
51
|
+
/** What `kt-submit` carries: the mode, what was written, and a way to wait for the sending. */
|
|
52
|
+
export interface KtAuthSubmitDetail {
|
|
53
|
+
readonly mode: KtAuthMode;
|
|
54
|
+
readonly values: {
|
|
55
|
+
readonly name?: string;
|
|
56
|
+
readonly email?: string;
|
|
57
|
+
readonly password?: string;
|
|
58
|
+
readonly code?: string;
|
|
59
|
+
readonly remember?: boolean;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Hand it the request: the button runs it, and if it rejects, its message
|
|
63
|
+
* is shown above the fields.
|
|
64
|
+
*/
|
|
65
|
+
readonly wait: (sending: Promise<unknown>) => void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The way into a product: signing in, creating an account, resetting a
|
|
69
|
+
* password, entering a code — one block, four modes.
|
|
70
|
+
*
|
|
71
|
+
* It checks what it can before sending — an email that is one, a password, a
|
|
72
|
+
* name, a whole code — and says so on the field. Then `kt-submit` carries the
|
|
73
|
+
* mode and the values, and `wait(promise)`: the button runs the request, and
|
|
74
|
+
* if it rejects, its message is shown above the fields. A reset that succeeds
|
|
75
|
+
* says the link is on its way.
|
|
76
|
+
*
|
|
77
|
+
* Sign-in providers go in `providers`; the links between modes move the form
|
|
78
|
+
* itself and fire `kt-mode`. Every word is in `texts`, English until given
|
|
79
|
+
* others.
|
|
80
|
+
*
|
|
81
|
+
* @element kt-auth-form
|
|
82
|
+
*
|
|
83
|
+
* @slot logo - Above the heading: the product's mark.
|
|
84
|
+
* @slot footer - Under the form: the terms, a privacy note.
|
|
85
|
+
*
|
|
86
|
+
* @csspart base - The block.
|
|
87
|
+
* @csspart form - The `<form>`.
|
|
88
|
+
*
|
|
89
|
+
* @fires kt-submit - The form was sent. `detail: { mode, values, wait(promise) }`.
|
|
90
|
+
* @fires kt-mode - The form moved to another mode. `detail: { mode }`.
|
|
91
|
+
* @fires kt-provider - A provider was chosen. `detail: { id }`.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```html
|
|
95
|
+
* <kt-auth-form remember></kt-auth-form>
|
|
96
|
+
* <script>
|
|
97
|
+
* form.addEventListener('kt-submit', (e) => e.detail.wait(auth.signIn(e.detail.values)));
|
|
98
|
+
* </script>
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare class KtAuthForm extends KtElement {
|
|
102
|
+
static styles: import("lit").CSSResultGroup[];
|
|
103
|
+
/** `sign-in`, `sign-up`, `forgot` or `code`. */
|
|
104
|
+
mode: KtAuthMode;
|
|
105
|
+
/** Sign-in providers, above the fields. Set as a property. */
|
|
106
|
+
providers: readonly KtAuthProvider[];
|
|
107
|
+
/** Shows "Keep me signed in" when signing in. */
|
|
108
|
+
remember: boolean;
|
|
109
|
+
/** The shortest password a new account may choose. */
|
|
110
|
+
passwordMin: number;
|
|
111
|
+
/** The heading's level, 1 to 6: 1 when the form is the page. */
|
|
112
|
+
headingLevel: number;
|
|
113
|
+
/** Drops the card around it, for a page that frames it already. */
|
|
114
|
+
plain: boolean;
|
|
115
|
+
/** An error to show above the fields; a failed sending sets it. */
|
|
116
|
+
error: string;
|
|
117
|
+
/** The words, English until given others: `form.texts = { signInHeading: 'Connexion' }`. */
|
|
118
|
+
texts: Partial<KtAuthTexts>;
|
|
119
|
+
/** A reset link was sent: the form says so instead of asking again. */
|
|
120
|
+
private sent;
|
|
121
|
+
private button;
|
|
122
|
+
private get t();
|
|
123
|
+
/** Moves the form to another mode, as its links do. */
|
|
124
|
+
goTo(mode: KtAuthMode): void;
|
|
125
|
+
private field;
|
|
126
|
+
/** Checks what can be checked before sending; marks each field it finds wanting. */
|
|
127
|
+
private check;
|
|
128
|
+
private values;
|
|
129
|
+
/** Sends the form, as its button does, once what it holds checks out. */
|
|
130
|
+
submit(): void;
|
|
131
|
+
private onSubmit;
|
|
132
|
+
/** Enter in a field sends, as in any form: the native field is inside the kt-input. */
|
|
133
|
+
private onKeyDown;
|
|
134
|
+
private input;
|
|
135
|
+
private fields;
|
|
136
|
+
private head;
|
|
137
|
+
render(): TemplateResult;
|
|
138
|
+
}
|
|
139
|
+
declare global {
|
|
140
|
+
interface HTMLElementTagNameMap {
|
|
141
|
+
'kt-auth-form': KtAuthForm;
|
|
142
|
+
}
|
|
143
|
+
}
|