@fusion.dev/helix-headless 0.0.0-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/custom-elements.json +75202 -0
- package/dist/accordion/index.d.ts +2 -0
- package/dist/accordion/index.js +2 -0
- package/dist/accordion/item/accordion-item.d.ts +141 -0
- package/dist/accordion/item/accordion-item.js +214 -0
- package/dist/accordion/root/accordion-root.d.ts +57 -0
- package/dist/accordion/root/accordion-root.js +96 -0
- package/dist/announcement/index.d.ts +1 -0
- package/dist/announcement/index.js +1 -0
- package/dist/announcement/root/announcement-root.d.ts +106 -0
- package/dist/announcement/root/announcement-root.js +144 -0
- package/dist/app-header/index.d.ts +1 -0
- package/dist/app-header/index.js +1 -0
- package/dist/app-header/root/app-header-root.d.ts +113 -0
- package/dist/app-header/root/app-header-root.js +147 -0
- package/dist/avatar/group/avatar-group.d.ts +116 -0
- package/dist/avatar/group/avatar-group.js +136 -0
- package/dist/avatar/index.d.ts +2 -0
- package/dist/avatar/index.js +2 -0
- package/dist/avatar/root/avatar-root.d.ts +71 -0
- package/dist/avatar/root/avatar-root.js +147 -0
- package/dist/badge/index.d.ts +1 -0
- package/dist/badge/index.js +1 -0
- package/dist/badge/root/badge-root.d.ts +61 -0
- package/dist/badge/root/badge-root.js +121 -0
- package/dist/bar-chart/index.d.ts +1 -0
- package/dist/bar-chart/index.js +1 -0
- package/dist/bar-chart/root/bar-chart-root.d.ts +266 -0
- package/dist/bar-chart/root/bar-chart-root.js +515 -0
- package/dist/blockquote/index.d.ts +1 -0
- package/dist/blockquote/index.js +1 -0
- package/dist/blockquote/root/blockquote-root.d.ts +51 -0
- package/dist/blockquote/root/blockquote-root.js +84 -0
- package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +104 -0
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +137 -0
- package/dist/breadcrumbs/index.d.ts +2 -0
- package/dist/breadcrumbs/index.js +2 -0
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +77 -0
- package/dist/breadcrumbs/root/breadcrumbs-root.js +94 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +1 -0
- package/dist/button/root/button-root.d.ts +251 -0
- package/dist/button/root/button-root.js +401 -0
- package/dist/button-group/index.d.ts +1 -0
- package/dist/button-group/index.js +1 -0
- package/dist/button-group/root/button-group-root.d.ts +62 -0
- package/dist/button-group/root/button-group-root.js +62 -0
- package/dist/callout/index.d.ts +1 -0
- package/dist/callout/index.js +1 -0
- package/dist/callout/root/callout-root.d.ts +72 -0
- package/dist/callout/root/callout-root.js +90 -0
- package/dist/card/index.d.ts +1 -0
- package/dist/card/index.js +1 -0
- package/dist/card/root/card-root.d.ts +54 -0
- package/dist/card/root/card-root.js +71 -0
- package/dist/carousel/index.d.ts +1 -0
- package/dist/carousel/index.js +1 -0
- package/dist/carousel/root/carousel-root.d.ts +399 -0
- package/dist/carousel/root/carousel-root.js +723 -0
- package/dist/chat/composer/chat-composer.d.ts +160 -0
- package/dist/chat/composer/chat-composer.js +273 -0
- package/dist/chat/index.d.ts +3 -0
- package/dist/chat/index.js +3 -0
- package/dist/chat/message/chat-message.d.ts +170 -0
- package/dist/chat/message/chat-message.js +271 -0
- package/dist/chat/root/chat-root.d.ts +216 -0
- package/dist/chat/root/chat-root.js +389 -0
- package/dist/checkbox/index.d.ts +1 -0
- package/dist/checkbox/index.js +1 -0
- package/dist/checkbox/root/checkbox-root.d.ts +108 -0
- package/dist/checkbox/root/checkbox-root.js +207 -0
- package/dist/checkbox-group/index.d.ts +1 -0
- package/dist/checkbox-group/index.js +1 -0
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +155 -0
- package/dist/checkbox-group/root/checkbox-group-root.js +293 -0
- package/dist/code/index.d.ts +1 -0
- package/dist/code/index.js +1 -0
- package/dist/code/root/code-root.d.ts +36 -0
- package/dist/code/root/code-root.js +43 -0
- package/dist/code-block/index.d.ts +1 -0
- package/dist/code-block/index.js +1 -0
- package/dist/code-block/root/code-block-root.d.ts +112 -0
- package/dist/code-block/root/code-block-root.js +155 -0
- package/dist/color-input/index.d.ts +1 -0
- package/dist/color-input/index.js +1 -0
- package/dist/color-input/root/color-input-root.d.ts +227 -0
- package/dist/color-input/root/color-input-root.js +456 -0
- package/dist/color-picker/color.d.ts +74 -0
- package/dist/color-picker/color.js +238 -0
- package/dist/color-picker/index.d.ts +2 -0
- package/dist/color-picker/index.js +2 -0
- package/dist/color-picker/root/color-picker-root.d.ts +303 -0
- package/dist/color-picker/root/color-picker-root.js +576 -0
- package/dist/combobox/index.d.ts +1 -0
- package/dist/combobox/index.js +1 -0
- package/dist/combobox/root/combobox-root.d.ts +396 -0
- package/dist/combobox/root/combobox-root.js +808 -0
- package/dist/command/index.d.ts +1 -0
- package/dist/command/index.js +1 -0
- package/dist/command/root/command-root.d.ts +166 -0
- package/dist/command/root/command-root.js +353 -0
- package/dist/copy-button/index.d.ts +1 -0
- package/dist/copy-button/index.js +1 -0
- package/dist/copy-button/root/copy-button-root.d.ts +154 -0
- package/dist/copy-button/root/copy-button-root.js +225 -0
- package/dist/data-table/index.d.ts +2 -0
- package/dist/data-table/index.js +2 -0
- package/dist/data-table/root/data-table-root.d.ts +392 -0
- package/dist/data-table/root/data-table-root.js +729 -0
- package/dist/data-table/root/order.d.ts +116 -0
- package/dist/data-table/root/order.js +175 -0
- package/dist/date-input/index.d.ts +1 -0
- package/dist/date-input/index.js +1 -0
- package/dist/date-input/root/date-input-root.d.ts +361 -0
- package/dist/date-input/root/date-input-root.js +791 -0
- package/dist/date-picker/index.d.ts +1 -0
- package/dist/date-picker/index.js +1 -0
- package/dist/date-picker/root/date-picker-root.d.ts +361 -0
- package/dist/date-picker/root/date-picker-root.js +837 -0
- package/dist/dialog/index.d.ts +1 -0
- package/dist/dialog/index.js +1 -0
- package/dist/dialog/root/dialog-root.d.ts +185 -0
- package/dist/dialog/root/dialog-root.js +319 -0
- package/dist/divider/index.d.ts +1 -0
- package/dist/divider/index.js +1 -0
- package/dist/divider/root/divider-root.d.ts +68 -0
- package/dist/divider/root/divider-root.js +116 -0
- package/dist/donut-chart/index.d.ts +1 -0
- package/dist/donut-chart/index.js +1 -0
- package/dist/donut-chart/root/donut-chart-root.d.ts +209 -0
- package/dist/donut-chart/root/donut-chart-root.js +395 -0
- package/dist/drawer/index.d.ts +1 -0
- package/dist/drawer/index.js +1 -0
- package/dist/drawer/root/drawer-root.d.ts +144 -0
- package/dist/drawer/root/drawer-root.js +222 -0
- package/dist/drilldown/index.d.ts +2 -0
- package/dist/drilldown/index.js +2 -0
- package/dist/drilldown/item/drilldown-item.d.ts +87 -0
- package/dist/drilldown/item/drilldown-item.js +152 -0
- package/dist/drilldown/root/drilldown-root.d.ts +115 -0
- package/dist/drilldown/root/drilldown-root.js +263 -0
- package/dist/element-events.generated.d.ts +1 -0
- package/dist/element-events.generated.js +125 -0
- package/dist/empty/index.d.ts +1 -0
- package/dist/empty/index.js +1 -0
- package/dist/empty/root/empty-root.d.ts +47 -0
- package/dist/empty/root/empty-root.js +68 -0
- package/dist/file-input/index.d.ts +1 -0
- package/dist/file-input/index.js +1 -0
- package/dist/file-input/root/file-input-root.d.ts +196 -0
- package/dist/file-input/root/file-input-root.js +488 -0
- package/dist/format-date/index.d.ts +1 -0
- package/dist/format-date/index.js +1 -0
- package/dist/format-date/root/format-date-root.d.ts +151 -0
- package/dist/format-date/root/format-date-root.js +266 -0
- package/dist/heatmap/index.d.ts +2 -0
- package/dist/heatmap/index.js +2 -0
- package/dist/heatmap/root/buckets.d.ts +62 -0
- package/dist/heatmap/root/buckets.js +85 -0
- package/dist/heatmap/root/heatmap-root.d.ts +330 -0
- package/dist/heatmap/root/heatmap-root.js +526 -0
- package/dist/icon/index.d.ts +1 -0
- package/dist/icon/index.js +1 -0
- package/dist/icon/root/icon-root.d.ts +53 -0
- package/dist/icon/root/icon-root.js +93 -0
- package/dist/icon-button/index.d.ts +1 -0
- package/dist/icon-button/index.js +1 -0
- package/dist/icon-button/root/icon-button-root.d.ts +40 -0
- package/dist/icon-button/root/icon-button-root.js +46 -0
- package/dist/image/index.d.ts +1 -0
- package/dist/image/index.js +1 -0
- package/dist/image/root/image-root.d.ts +213 -0
- package/dist/image/root/image-root.js +335 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +99 -0
- package/dist/input/index.d.ts +1 -0
- package/dist/input/index.js +1 -0
- package/dist/input/root/input-root.d.ts +281 -0
- package/dist/input/root/input-root.js +472 -0
- package/dist/kbd/index.d.ts +1 -0
- package/dist/kbd/index.js +1 -0
- package/dist/kbd/root/kbd-root.d.ts +98 -0
- package/dist/kbd/root/kbd-root.js +230 -0
- package/dist/legend/index.d.ts +1 -0
- package/dist/legend/index.js +1 -0
- package/dist/legend/root/legend-root.d.ts +83 -0
- package/dist/legend/root/legend-root.js +125 -0
- package/dist/line-chart/index.d.ts +1 -0
- package/dist/line-chart/index.js +1 -0
- package/dist/line-chart/root/line-chart-root.d.ts +238 -0
- package/dist/line-chart/root/line-chart-root.js +365 -0
- package/dist/list/index.d.ts +2 -0
- package/dist/list/index.js +2 -0
- package/dist/list/item/list-item.d.ts +95 -0
- package/dist/list/item/list-item.js +148 -0
- package/dist/list/root/list-root.d.ts +72 -0
- package/dist/list/root/list-root.js +61 -0
- package/dist/listbox/index.d.ts +1 -0
- package/dist/listbox/index.js +1 -0
- package/dist/listbox/root/listbox-root.d.ts +357 -0
- package/dist/listbox/root/listbox-root.js +890 -0
- package/dist/loader/index.d.ts +1 -0
- package/dist/loader/index.js +1 -0
- package/dist/loader/root/loader-root.d.ts +52 -0
- package/dist/loader/root/loader-root.js +74 -0
- package/dist/megamenu/index.d.ts +1 -0
- package/dist/megamenu/index.js +1 -0
- package/dist/megamenu/root/megamenu-root.d.ts +254 -0
- package/dist/megamenu/root/megamenu-root.js +526 -0
- package/dist/menu/index.d.ts +2 -0
- package/dist/menu/index.js +2 -0
- package/dist/menu/item/menu-item.d.ts +231 -0
- package/dist/menu/item/menu-item.js +405 -0
- package/dist/menu/root/menu-root.d.ts +158 -0
- package/dist/menu/root/menu-root.js +422 -0
- package/dist/meter/index.d.ts +1 -0
- package/dist/meter/index.js +1 -0
- package/dist/meter/root/meter-root.d.ts +234 -0
- package/dist/meter/root/meter-root.js +316 -0
- package/dist/navigation/index.d.ts +1 -0
- package/dist/navigation/index.js +1 -0
- package/dist/navigation/root/navigation-root.d.ts +55 -0
- package/dist/navigation/root/navigation-root.js +130 -0
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.js +2 -0
- package/dist/notifications/notification/notification.d.ts +181 -0
- package/dist/notifications/notification/notification.js +305 -0
- package/dist/notifications/root/notifications-root.d.ts +168 -0
- package/dist/notifications/root/notifications-root.js +291 -0
- package/dist/number-input/index.d.ts +1 -0
- package/dist/number-input/index.js +1 -0
- package/dist/number-input/root/number-input-root.d.ts +282 -0
- package/dist/number-input/root/number-input-root.js +592 -0
- package/dist/option/group/option-group.d.ts +38 -0
- package/dist/option/group/option-group.js +58 -0
- package/dist/option/index.d.ts +2 -0
- package/dist/option/index.js +2 -0
- package/dist/option/root/option-root.d.ts +194 -0
- package/dist/option/root/option-root.js +247 -0
- package/dist/outline/index.d.ts +1 -0
- package/dist/outline/index.js +1 -0
- package/dist/outline/root/outline-root.d.ts +237 -0
- package/dist/outline/root/outline-root.js +519 -0
- package/dist/pagination/index.d.ts +3 -0
- package/dist/pagination/index.js +3 -0
- package/dist/pagination/root/pagination-root.d.ts +390 -0
- package/dist/pagination/root/pagination-root.js +695 -0
- package/dist/pagination/size/pagination-size.d.ts +127 -0
- package/dist/pagination/size/pagination-size.js +232 -0
- package/dist/pagination/summary/pagination-summary.d.ts +90 -0
- package/dist/pagination/summary/pagination-summary.js +145 -0
- package/dist/pin-input/index.d.ts +1 -0
- package/dist/pin-input/index.js +1 -0
- package/dist/pin-input/root/pin-input-root.d.ts +292 -0
- package/dist/pin-input/root/pin-input-root.js +553 -0
- package/dist/popover/index.d.ts +1 -0
- package/dist/popover/index.js +1 -0
- package/dist/popover/root/popover-root.d.ts +108 -0
- package/dist/popover/root/popover-root.js +231 -0
- package/dist/progress/index.d.ts +1 -0
- package/dist/progress/index.js +1 -0
- package/dist/progress/root/progress-root.d.ts +125 -0
- package/dist/progress/root/progress-root.js +212 -0
- package/dist/qr-code/index.d.ts +1 -0
- package/dist/qr-code/index.js +1 -0
- package/dist/qr-code/root/qr-code-root.d.ts +93 -0
- package/dist/qr-code/root/qr-code-root.js +233 -0
- package/dist/quote/index.d.ts +1 -0
- package/dist/quote/index.js +1 -0
- package/dist/quote/root/quote-root.d.ts +36 -0
- package/dist/quote/root/quote-root.js +50 -0
- package/dist/radio/group/radio-group.d.ts +84 -0
- package/dist/radio/group/radio-group.js +170 -0
- package/dist/radio/index.d.ts +2 -0
- package/dist/radio/index.js +2 -0
- package/dist/radio/root/radio-root.d.ts +102 -0
- package/dist/radio/root/radio-root.js +151 -0
- package/dist/rating/index.d.ts +1 -0
- package/dist/rating/index.js +1 -0
- package/dist/rating/root/rating-root.d.ts +242 -0
- package/dist/rating/root/rating-root.js +484 -0
- package/dist/scroll-area/index.d.ts +1 -0
- package/dist/scroll-area/index.js +1 -0
- package/dist/scroll-area/root/scroll-area-root.d.ts +96 -0
- package/dist/scroll-area/root/scroll-area-root.js +215 -0
- package/dist/select/index.d.ts +1 -0
- package/dist/select/index.js +1 -0
- package/dist/select/root/select-root.d.ts +170 -0
- package/dist/select/root/select-root.js +419 -0
- package/dist/skeleton/index.d.ts +1 -0
- package/dist/skeleton/index.js +1 -0
- package/dist/skeleton/root/skeleton-root.d.ts +31 -0
- package/dist/skeleton/root/skeleton-root.js +42 -0
- package/dist/slider/index.d.ts +1 -0
- package/dist/slider/index.js +1 -0
- package/dist/slider/root/slider-root.d.ts +235 -0
- package/dist/slider/root/slider-root.js +488 -0
- package/dist/sparkline/index.d.ts +1 -0
- package/dist/sparkline/index.js +1 -0
- package/dist/sparkline/root/sparkline-root.d.ts +147 -0
- package/dist/sparkline/root/sparkline-root.js +278 -0
- package/dist/speed-dial/index.d.ts +2 -0
- package/dist/speed-dial/index.js +2 -0
- package/dist/speed-dial/item/speed-dial-item.d.ts +71 -0
- package/dist/speed-dial/item/speed-dial-item.js +113 -0
- package/dist/speed-dial/root/speed-dial-root.d.ts +122 -0
- package/dist/speed-dial/root/speed-dial-root.js +251 -0
- package/dist/splitter/index.d.ts +1 -0
- package/dist/splitter/index.js +1 -0
- package/dist/splitter/root/splitter-root.d.ts +491 -0
- package/dist/splitter/root/splitter-root.js +1030 -0
- package/dist/stat/group/stat-group.d.ts +30 -0
- package/dist/stat/group/stat-group.js +47 -0
- package/dist/stat/index.d.ts +2 -0
- package/dist/stat/index.js +2 -0
- package/dist/stat/root/stat-root.d.ts +62 -0
- package/dist/stat/root/stat-root.js +51 -0
- package/dist/steps/index.d.ts +2 -0
- package/dist/steps/index.js +2 -0
- package/dist/steps/root/steps-root.d.ts +110 -0
- package/dist/steps/root/steps-root.js +130 -0
- package/dist/steps/step/step.d.ts +139 -0
- package/dist/steps/step/step.js +215 -0
- package/dist/switch/index.d.ts +1 -0
- package/dist/switch/index.js +1 -0
- package/dist/switch/root/switch-root.d.ts +74 -0
- package/dist/switch/root/switch-root.js +153 -0
- package/dist/table/cell/table-cell.d.ts +34 -0
- package/dist/table/cell/table-cell.js +52 -0
- package/dist/table/column/table-column.d.ts +165 -0
- package/dist/table/column/table-column.js +222 -0
- package/dist/table/index.d.ts +4 -0
- package/dist/table/index.js +4 -0
- package/dist/table/root/table-root.d.ts +98 -0
- package/dist/table/root/table-root.js +161 -0
- package/dist/table/row/table-row.d.ts +27 -0
- package/dist/table/row/table-row.js +42 -0
- package/dist/tabs/index.d.ts +3 -0
- package/dist/tabs/index.js +3 -0
- package/dist/tabs/panel/tab-panel.d.ts +67 -0
- package/dist/tabs/panel/tab-panel.js +98 -0
- package/dist/tabs/root/tabs-root.d.ts +123 -0
- package/dist/tabs/root/tabs-root.js +217 -0
- package/dist/tabs/tab/tab.d.ts +64 -0
- package/dist/tabs/tab/tab.js +116 -0
- package/dist/tag/index.d.ts +1 -0
- package/dist/tag/index.js +1 -0
- package/dist/tag/root/tag-root.d.ts +41 -0
- package/dist/tag/root/tag-root.js +67 -0
- package/dist/textarea/index.d.ts +1 -0
- package/dist/textarea/index.js +1 -0
- package/dist/textarea/root/textarea-root.d.ts +215 -0
- package/dist/textarea/root/textarea-root.js +380 -0
- package/dist/time-input/index.d.ts +1 -0
- package/dist/time-input/index.js +1 -0
- package/dist/time-input/root/time-input-root.d.ts +322 -0
- package/dist/time-input/root/time-input-root.js +661 -0
- package/dist/time-picker/index.d.ts +1 -0
- package/dist/time-picker/index.js +1 -0
- package/dist/time-picker/root/time-picker-root.d.ts +380 -0
- package/dist/time-picker/root/time-picker-root.js +669 -0
- package/dist/timeline/index.d.ts +2 -0
- package/dist/timeline/index.js +2 -0
- package/dist/timeline/item/timeline-item.d.ts +81 -0
- package/dist/timeline/item/timeline-item.js +128 -0
- package/dist/timeline/root/timeline-root.d.ts +52 -0
- package/dist/timeline/root/timeline-root.js +72 -0
- package/dist/toast/index.d.ts +2 -0
- package/dist/toast/index.js +2 -0
- package/dist/toast/item/toast-item.d.ts +183 -0
- package/dist/toast/item/toast-item.js +345 -0
- package/dist/toast/root/toast-root.d.ts +72 -0
- package/dist/toast/root/toast-root.js +87 -0
- package/dist/toggle/group/toggle-group.d.ts +140 -0
- package/dist/toggle/group/toggle-group.js +278 -0
- package/dist/toggle/index.d.ts +2 -0
- package/dist/toggle/index.js +2 -0
- package/dist/toggle/root/toggle-root.d.ts +99 -0
- package/dist/toggle/root/toggle-root.js +146 -0
- package/dist/toolbar/index.d.ts +1 -0
- package/dist/toolbar/index.js +1 -0
- package/dist/toolbar/root/toolbar-root.d.ts +105 -0
- package/dist/toolbar/root/toolbar-root.js +260 -0
- package/dist/tooltip/index.d.ts +1 -0
- package/dist/tooltip/index.js +1 -0
- package/dist/tooltip/root/tooltip-root.d.ts +388 -0
- package/dist/tooltip/root/tooltip-root.js +707 -0
- package/dist/tour/index.d.ts +2 -0
- package/dist/tour/index.js +2 -0
- package/dist/tour/root/tour-root.d.ts +161 -0
- package/dist/tour/root/tour-root.js +351 -0
- package/dist/tour/stop/tour-stop.d.ts +131 -0
- package/dist/tour/stop/tour-stop.js +249 -0
- package/dist/tree/index.d.ts +2 -0
- package/dist/tree/index.js +2 -0
- package/dist/tree/item/tree-item.d.ts +208 -0
- package/dist/tree/item/tree-item.js +381 -0
- package/dist/tree/root/tree-root.d.ts +88 -0
- package/dist/tree/root/tree-root.js +287 -0
- package/dist/trend/index.d.ts +1 -0
- package/dist/trend/index.js +1 -0
- package/dist/trend/root/trend-root.d.ts +67 -0
- package/dist/trend/root/trend-root.js +106 -0
- package/dist/video/index.d.ts +1 -0
- package/dist/video/index.js +1 -0
- package/dist/video/root/video-root.d.ts +711 -0
- package/dist/video/root/video-root.js +1821 -0
- package/dist/video-playlist/index.d.ts +3 -0
- package/dist/video-playlist/index.js +3 -0
- package/dist/video-playlist/item/video-playlist-item.d.ts +101 -0
- package/dist/video-playlist/item/video-playlist-item.js +162 -0
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +121 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +237 -0
- package/dist/video-playlist/root/video-playlist-root.d.ts +108 -0
- package/dist/video-playlist/root/video-playlist-root.js +218 -0
- package/dist/virtual-list/index.d.ts +1 -0
- package/dist/virtual-list/index.js +1 -0
- package/dist/virtual-list/root/virtual-list-root.d.ts +121 -0
- package/dist/virtual-list/root/virtual-list-root.js +221 -0
- package/jsx.d.ts +49767 -0
- package/package.json +77 -0
- package/svelte.d.ts +129 -0
- package/vue.d.ts +9242 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { AriaForwardController, customElement, HelixCreateEvent, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, PopoverController, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { caretDown } from '@fusion.dev/helix-icons';
|
|
9
|
+
import { nothing } from 'lit';
|
|
10
|
+
import { property, state } from 'lit/decorators.js';
|
|
11
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
+
import { html } from 'lit/static-html.js';
|
|
13
|
+
/* Side-effect import, because this renders a listbox in its own shadow root and
|
|
14
|
+
a custom element registers on import. The path is relative, so this registers
|
|
15
|
+
the base listbox and the styled file's identical import registers the styled
|
|
16
|
+
one; `tier` picks which name the template asks for. */
|
|
17
|
+
import '../../listbox/index.js';
|
|
18
|
+
/* And the tags, for the same reason: `multiple` renders one per chosen option
|
|
19
|
+
and a custom element registers on import. */
|
|
20
|
+
import '../../tag/index.js';
|
|
21
|
+
/**
|
|
22
|
+
* Whether an edit added characters rather than taking them away.
|
|
23
|
+
*
|
|
24
|
+
* `beforeinput` and `input` both carry `inputType`, and it is the platform's
|
|
25
|
+
* own answer to what the reader just did: `insertText` for typing,
|
|
26
|
+
* `insertFromPaste` for a paste, and one of several `delete*` for the ways of
|
|
27
|
+
* removing. Anything without one is treated as an insertion, because the only
|
|
28
|
+
* events with no `inputType` here are the ones a script dispatches, and a test
|
|
29
|
+
* or a consumer setting the value means it to behave like typing.
|
|
30
|
+
*/
|
|
31
|
+
const isInsertion = (event) => !event.inputType?.startsWith('delete');
|
|
32
|
+
/**
|
|
33
|
+
* @summary A field that filters a list as it is typed into.
|
|
34
|
+
*
|
|
35
|
+
* @description
|
|
36
|
+
* **The list is `helix-listbox`, and the filtering is its own.** It already
|
|
37
|
+
* owns narrowing, the empty state, the highlight, `aria-activedescendant` and
|
|
38
|
+
* scrolling an option into view, and every one of those is a decision with a
|
|
39
|
+
* long note attached. Rewriting them here would be a second copy to keep in
|
|
40
|
+
* step; what is left for this element is the field, the panel and the traffic
|
|
41
|
+
* between them.
|
|
42
|
+
*
|
|
43
|
+
* **The text and the value are two things.** What a reader types is a filter,
|
|
44
|
+
* not an answer: it narrows the list and is thrown away. The value is whichever
|
|
45
|
+
* option they then choose, and the field is rewritten to that option's words
|
|
46
|
+
* once they have. A combobox that treated the typing as the value would submit
|
|
47
|
+
* "jav" to a form, which is the mistake a plain input makes and the reason this
|
|
48
|
+
* component exists.
|
|
49
|
+
*
|
|
50
|
+
* That is also why the field is emptied of its filter on close rather than left
|
|
51
|
+
* holding it: a control reopened still narrowed by the last visit's search is a
|
|
52
|
+
* control whose second use starts from the first one's leftovers. Select
|
|
53
|
+
* records the same, having been fixed for it.
|
|
54
|
+
*
|
|
55
|
+
* **Focus stays in the field, which is the whole of what a combobox is.** The
|
|
56
|
+
* arrows move the list without leaving the text, which is why the listbox has a
|
|
57
|
+
* public `navigate`: the moving, the wrapping, the skipping of disabled options
|
|
58
|
+
* and the seeding from the current value all stay in the one place that already
|
|
59
|
+
* does them.
|
|
60
|
+
*
|
|
61
|
+
* `role="combobox"` on the input rather than on a wrapper, because the thing
|
|
62
|
+
* the pattern describes is the text field, and `aria-controls` can name the
|
|
63
|
+
* listbox because both are in this shadow root. The active option cannot be
|
|
64
|
+
* named that way, being the consumer's light DOM, so it is linked by element
|
|
65
|
+
* reference for the reason Listbox records at length.
|
|
66
|
+
*
|
|
67
|
+
* **With `multiple`, the chosen options are tags in the field.** A field that
|
|
68
|
+
* says "Rust, Ruby, Go" is a sentence the reader has to parse to find out what
|
|
69
|
+
* is in it, and offers nothing to press to take one out: Select does that today
|
|
70
|
+
* and it is the weakest part of it. A tag apiece is one object per answer, each
|
|
71
|
+
* with its own remove control, which is what the reader already believes they
|
|
72
|
+
* are looking at.
|
|
73
|
+
*
|
|
74
|
+
* The panel stays open while more than one can be chosen, because a control
|
|
75
|
+
* that shut after each pick is one the reader has to reopen to keep using. The
|
|
76
|
+
* filter clears on each choice instead, so the next pick starts from the whole
|
|
77
|
+
* list rather than from the last search.
|
|
78
|
+
*
|
|
79
|
+
* **`creatable` announces, and does not write.** The options are the consumer's
|
|
80
|
+
* light DOM and nothing here puts an element into it: a component that grew the
|
|
81
|
+
* consumer's markup would be a component their framework re-renders over. What
|
|
82
|
+
* this does is offer a row saying what would be added, take the value when the
|
|
83
|
+
* reader presses it, and say so with `helix-create`. A consumer who has to
|
|
84
|
+
* validate first refuses the event and adds nothing.
|
|
85
|
+
*
|
|
86
|
+
* Which leaves a value naming no option, and that is what the words fall back
|
|
87
|
+
* to: for anything chosen the field shows the option's text if there is one and
|
|
88
|
+
* the value itself otherwise. No copy is stored, so a consumer who then adds a
|
|
89
|
+
* real option with that value has its words take over on the next render.
|
|
90
|
+
*
|
|
91
|
+
* **`suggest` finishes the word, and only ever forwards.** The field completes
|
|
92
|
+
* to the first option the typing begins, with the part the reader did not type
|
|
93
|
+
* selected, so the next character replaces it and Delete removes it. Applied on
|
|
94
|
+
* insertion alone: completing after a deletion would put back what the reader
|
|
95
|
+
* just took out, and a field that cannot be shortened is a field that cannot be
|
|
96
|
+
* corrected. Measured as the difference between `insertText` and the several
|
|
97
|
+
* `delete*` input types, which is what the platform already says about which
|
|
98
|
+
* happened.
|
|
99
|
+
*
|
|
100
|
+
* The filter stays what was typed rather than what was completed. Filtering on
|
|
101
|
+
* the completion would narrow the list to the one option it came from, so a
|
|
102
|
+
* reader typing "ru" would be shown "Rust" alone and never learn Ruby was
|
|
103
|
+
* there.
|
|
104
|
+
*
|
|
105
|
+
* @since 0.1.0
|
|
106
|
+
* @status experimental
|
|
107
|
+
* @category forms
|
|
108
|
+
* @aliases Autocomplete, Typeahead, Search select
|
|
109
|
+
* @documentation https://helix.fusion.dev/components/combobox
|
|
110
|
+
*
|
|
111
|
+
* @slot - The options, which are `helix-option` elements.
|
|
112
|
+
* @slot end - After it, for a unit or a clear button.
|
|
113
|
+
* @slot indicator - Replaces the caret saying the field opens.
|
|
114
|
+
* @slot start - Before the text, for an icon.
|
|
115
|
+
*
|
|
116
|
+
* @event {Event} change - Announced once the value has moved.
|
|
117
|
+
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before the chosen option changes. Cancelable: refusing it takes the decision and nothing moves.
|
|
118
|
+
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes. Cancelable: refusing it takes the decision and nothing moves.
|
|
119
|
+
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before the panel opens. Cancelable: refusing it takes the decision and nothing moves.
|
|
120
|
+
* @event {HelixCreateEvent} helix-create - Fired when the reader asks for something the list does not hold, and cancelable.
|
|
121
|
+
* @event {Event} input - Announced once the value has moved.
|
|
122
|
+
* @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
|
|
123
|
+
*
|
|
124
|
+
* @csspart control - The text field itself.
|
|
125
|
+
* @csspart create - The row offering to add what was typed.
|
|
126
|
+
* @csspart feedback - What is wrong with it.
|
|
127
|
+
* @csspart field - The box around the text and its marks.
|
|
128
|
+
* @csspart hint - Help under the field, while nothing is wrong.
|
|
129
|
+
* @csspart indicator - The caret.
|
|
130
|
+
* @csspart label - The question, over the field.
|
|
131
|
+
* @csspart listbox - The list inside it.
|
|
132
|
+
* @csspart listbox-empty - Shown when a filter has hidden everything.
|
|
133
|
+
* @csspart listbox-feedback - What is wrong with it.
|
|
134
|
+
* @csspart listbox-filter-icon - The mark beside that field.
|
|
135
|
+
* @csspart listbox-filter-mark - The glyph beside the filter field, where none is slotted.
|
|
136
|
+
* @csspart listbox-hint - Help under the list.
|
|
137
|
+
* @csspart listbox-items - The list itself.
|
|
138
|
+
* @csspart listbox-label - The question, over the list.
|
|
139
|
+
* @csspart listbox-loading - Shown while the options are still on their way.
|
|
140
|
+
* from the listbox.
|
|
141
|
+
* @csspart listbox-search - The region holding the field that narrows the list.
|
|
142
|
+
* @csspart listbox-search-field - The field itself.
|
|
143
|
+
* @csspart rest - The count of choices held beyond `max-tags`.
|
|
144
|
+
* @csspart surface - The floating panel.
|
|
145
|
+
* @csspart tag - One chosen option.
|
|
146
|
+
* @csspart tag-dismiss - The remove button, when `dismissible`.
|
|
147
|
+
* @csspart tag-dismiss-mark - The glyph on the dismiss control, where none is slotted.
|
|
148
|
+
* @csspart tag-prefix - The category before the label.
|
|
149
|
+
* @csspart tags - The row of chosen options, when `multiple` is set.
|
|
150
|
+
*/
|
|
151
|
+
let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerOpen(MixinControllerValue(MixinField(HelixFormElement))) {
|
|
152
|
+
constructor() {
|
|
153
|
+
super(...arguments);
|
|
154
|
+
/* A consumer's `aria-describedby` names ids in their own tree and the control
|
|
155
|
+
is in this shadow root, so the reference has to be carried down as an
|
|
156
|
+
element rather than copied as an id. See `AriaForwardController`: measured
|
|
157
|
+
in Chrome's own tree, the host held the description and the control the
|
|
158
|
+
reader is actually on was exposed without it. */
|
|
159
|
+
this.aria = new AriaForwardController(this, {
|
|
160
|
+
target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
|
|
161
|
+
});
|
|
162
|
+
/**
|
|
163
|
+
* What the browser may autofill in the text field, as a platform token.
|
|
164
|
+
*
|
|
165
|
+
* **Defaults to `off`, which is not the platform's default and is the
|
|
166
|
+
* point.** The field filters a list rather than collecting a value, so a
|
|
167
|
+
* browser offering to fill it with a saved address is competing for the same
|
|
168
|
+
* keystrokes as the listbox. A consumer who wants autofill can still ask.
|
|
169
|
+
*/
|
|
170
|
+
this.autocomplete = 'off';
|
|
171
|
+
/**
|
|
172
|
+
* Text shown while nothing has been chosen.
|
|
173
|
+
*/
|
|
174
|
+
this.placeholder = '';
|
|
175
|
+
/**
|
|
176
|
+
* Whether more than one option can be chosen.
|
|
177
|
+
*
|
|
178
|
+
* The list already does the work: `value` comes apart on commas, a press
|
|
179
|
+
* toggles rather than replaces, and the arrows move without choosing. What
|
|
180
|
+
* this adds is the tags, and keeping the panel open between picks.
|
|
181
|
+
*
|
|
182
|
+
* A comma is therefore not available inside an option's value, which is the
|
|
183
|
+
* listbox's contract and not this element's to change.
|
|
184
|
+
*/
|
|
185
|
+
this.multiple = false;
|
|
186
|
+
/**
|
|
187
|
+
* Whether the reader may answer with something the list does not hold.
|
|
188
|
+
*
|
|
189
|
+
* The row appears while what is typed matches no option's words exactly, so
|
|
190
|
+
* it is offered next to near misses too: a reader typing "Ruby on Rails" with
|
|
191
|
+
* "Ruby" in the list wants the one they are typing, not the one they are
|
|
192
|
+
* halfway through.
|
|
193
|
+
*/
|
|
194
|
+
this.creatable = false;
|
|
195
|
+
/**
|
|
196
|
+
* Whether the field finishes the word as it is typed.
|
|
197
|
+
*
|
|
198
|
+
* `suggest` rather than `autocomplete`, which is a real HTML attribute this
|
|
199
|
+
* element already sets on the input it renders, and two things of one name
|
|
200
|
+
* meaning different things on the same control is a question a consumer
|
|
201
|
+
* should not have to ask.
|
|
202
|
+
*/
|
|
203
|
+
this.suggest = false;
|
|
204
|
+
/**
|
|
205
|
+
* What the create row says, before the words it would add.
|
|
206
|
+
*/
|
|
207
|
+
this.createLabel = 'Add';
|
|
208
|
+
/**
|
|
209
|
+
* What a tag's remove control is called, since the mark has no name.
|
|
210
|
+
*/
|
|
211
|
+
this.removeLabel = 'Remove';
|
|
212
|
+
/**
|
|
213
|
+
* How many tags to draw before the rest become a count. `multiple` only.
|
|
214
|
+
*
|
|
215
|
+
* `0` is all of them, and is the default: a cap that guesses is worse than no
|
|
216
|
+
* cap, because the number that fits depends on the width of the field and the
|
|
217
|
+
* length of what is in it, neither of which this element knows. What a cap is
|
|
218
|
+
* genuinely for is a field in a row or a table cell, where a set of twelve
|
|
219
|
+
* choices would push everything below it down a line at a time.
|
|
220
|
+
*
|
|
221
|
+
* The remainder is counted rather than hidden. `+3` is a fact a reader can act
|
|
222
|
+
* on, where a row that silently stops is a field that appears to have lost
|
|
223
|
+
* what was put in it. Date Input and Time Input carry the same property and
|
|
224
|
+
* the same reasoning.
|
|
225
|
+
*/
|
|
226
|
+
this.maxTags = 0;
|
|
227
|
+
/**
|
|
228
|
+
* Accessible name for the list, which has no visible one of its own.
|
|
229
|
+
*/
|
|
230
|
+
this.listLabel = 'Options';
|
|
231
|
+
/**
|
|
232
|
+
* Whether the options are still on their way.
|
|
233
|
+
*
|
|
234
|
+
* Passed straight to the list, which is where the rows are and so where the
|
|
235
|
+
* message belongs.
|
|
236
|
+
*/
|
|
237
|
+
this.loading = false;
|
|
238
|
+
/**
|
|
239
|
+
* What the list says while they are on their way.
|
|
240
|
+
*/
|
|
241
|
+
this.loadingText = 'Loading';
|
|
242
|
+
/**
|
|
243
|
+
* Words shown where the filter has hidden everything.
|
|
244
|
+
*/
|
|
245
|
+
this.emptyText = 'No matches';
|
|
246
|
+
/**
|
|
247
|
+
* What the reader has typed, which narrows the list and is not the value.
|
|
248
|
+
*
|
|
249
|
+
* State rather than a property, because it is a keystroke rather than API: a
|
|
250
|
+
* consumer sets `value` to say what is chosen, and nothing outside needs to
|
|
251
|
+
* say what is half-typed.
|
|
252
|
+
*/
|
|
253
|
+
this.typed = '';
|
|
254
|
+
/**
|
|
255
|
+
* The completed words, where the field is finishing one, and empty otherwise.
|
|
256
|
+
*
|
|
257
|
+
* Held apart from `typed` rather than replacing it, because they are needed
|
|
258
|
+
* for different things at the same moment: this one is what the input shows,
|
|
259
|
+
* `typed` is what the list is filtered by, and the selection between them is
|
|
260
|
+
* what the reader is offered.
|
|
261
|
+
*/
|
|
262
|
+
this.suggestion = '';
|
|
263
|
+
this.panel = new PopoverController(this, {
|
|
264
|
+
anchor: () => this.renderRoot?.querySelector('[part="field"]'),
|
|
265
|
+
/* The surface *and* the field, for the reason four components here were
|
|
266
|
+
fixed for: scoped to the surface alone, a press on the field counts as
|
|
267
|
+
outside, so `pointerdown` closes the panel and the field's own handler
|
|
268
|
+
reopens it on the `click` that follows. */
|
|
269
|
+
boundary: () => [
|
|
270
|
+
this.renderRoot?.querySelector('[part="surface"]'),
|
|
271
|
+
this.renderRoot?.querySelector('[part="field"]'),
|
|
272
|
+
],
|
|
273
|
+
offset: () => 4,
|
|
274
|
+
onDismiss: () => this.close(),
|
|
275
|
+
placement: 'bottom-start',
|
|
276
|
+
surface: () => this.renderRoot?.querySelector('[part="surface"]'),
|
|
277
|
+
});
|
|
278
|
+
this.handleInput = (event) => {
|
|
279
|
+
// Stopped for the reason `Input.handleInput` gives: the inner control's own
|
|
280
|
+
// `input` is composed and would reach a consumer beside this element's.
|
|
281
|
+
event.stopPropagation();
|
|
282
|
+
const typed = event.target.value;
|
|
283
|
+
this.typed = typed;
|
|
284
|
+
/* The list is narrowed before the completion is looked for, because the
|
|
285
|
+
completion is the first thing still showing and that is not known until
|
|
286
|
+
the filter has run. */
|
|
287
|
+
this.listbox?.requestFilter(typed);
|
|
288
|
+
this.suggestion = this.suggest && isInsertion(event) ? this.completionFor(typed) : '';
|
|
289
|
+
if (!this.open) {
|
|
290
|
+
this.requestOpen(true);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* The keys the field answers, and the ones it hands on.
|
|
295
|
+
*
|
|
296
|
+
* Everything that moves within the list goes to the list, which is what
|
|
297
|
+
* `navigate` is for. Escape and Tab are the field's, because they are about
|
|
298
|
+
* the panel rather than about the options in it.
|
|
299
|
+
*/
|
|
300
|
+
this.handleKeydown = (event) => {
|
|
301
|
+
/* The input carries `disabled` too, so a reader never gets this far: the
|
|
302
|
+
platform suppresses the event before any listener sees it. Said here
|
|
303
|
+
anyway, because then the element's own state is what refuses rather than
|
|
304
|
+
a detail of the node it happens to render, and a consumer dispatching a
|
|
305
|
+
key at it gets the same answer a reader would. */
|
|
306
|
+
if (this.isDisabled) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (event.key === 'Escape') {
|
|
310
|
+
if (this.open) {
|
|
311
|
+
event.preventDefault();
|
|
312
|
+
this.close();
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (event.key === 'Tab') {
|
|
317
|
+
if (this.open) {
|
|
318
|
+
this.requestOpen(false);
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
/* Backspace on an empty field takes the last tag off.
|
|
323
|
+
*
|
|
324
|
+
* Only when empty, so it never competes with deleting a character, and
|
|
325
|
+
* only the last, because that is the one the caret is against. This is the
|
|
326
|
+
* keyboard's version of pressing a tag's remove control, and without it a
|
|
327
|
+
* reader who chose four things with the keyboard has to reach for a pointer
|
|
328
|
+
* to undo one.
|
|
329
|
+
*/
|
|
330
|
+
if (event.key === 'Backspace' && this.multiple && this.typed === '') {
|
|
331
|
+
const last = this.values.at(-1);
|
|
332
|
+
if (last !== undefined) {
|
|
333
|
+
event.preventDefault();
|
|
334
|
+
this.drop(last);
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
/* Enter with nothing left in the list creates, where there is anything to
|
|
339
|
+
create. Only then: with matches showing, Enter belongs to whichever one
|
|
340
|
+
the arrows are on, and a reader pressing it over a highlighted "Rust"
|
|
341
|
+
meant Rust. The row is still there to be pressed for the other case. */
|
|
342
|
+
if (event.key === 'Enter' && this.creation !== '' && !this.showing) {
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
this.create();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
/* Opening on the first arrow, before the list is asked to move: a closed
|
|
348
|
+
combobox pressed Down should show its options and land on the first,
|
|
349
|
+
which is one gesture to the reader and two steps here. */
|
|
350
|
+
if (!this.open && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
|
|
351
|
+
this.requestOpen(true);
|
|
352
|
+
}
|
|
353
|
+
this.listbox?.navigate(event);
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* Takes what the list announced and stops it there.
|
|
357
|
+
*
|
|
358
|
+
* The listbox says `helix-before-change` under its own name, and a consumer
|
|
359
|
+
* listening on this element would otherwise hear two changes for one choice,
|
|
360
|
+
* one of them naming a control they did not put on the page.
|
|
361
|
+
*/
|
|
362
|
+
this.handleChange = (event) => {
|
|
363
|
+
event.stopPropagation();
|
|
364
|
+
/* From the event rather than from the listbox, which is one choice behind:
|
|
365
|
+
it carries what is being *proposed*, dispatched before it is applied,
|
|
366
|
+
which is what makes it refusable. The listbox never applies it anyway,
|
|
367
|
+
since this element passes `.value` down and that is a consumer declaring
|
|
368
|
+
ownership. Select was fixed for exactly this. */
|
|
369
|
+
const { value } = event.detail;
|
|
370
|
+
/* And nothing happens on a refusal. Refusing a change should leave the
|
|
371
|
+
control where it was, not clear the filter and shut the panel as though
|
|
372
|
+
it had been taken. */
|
|
373
|
+
if (!this.requestValue(value)) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
// Either way the search is spent: it found what it was for.
|
|
377
|
+
this.typed = '';
|
|
378
|
+
this.suggestion = '';
|
|
379
|
+
this.listbox?.requestFilter('');
|
|
380
|
+
if (!this.multiple) {
|
|
381
|
+
this.close();
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* Anywhere in the field opens the list, and puts the caret where typing goes.
|
|
386
|
+
*
|
|
387
|
+
* On the field rather than on the `<input>`, which is where it was. The caret
|
|
388
|
+
* is a sibling of the input and so is the tag strip, so pressing the one part
|
|
389
|
+
* of the control that visibly says "this opens" did nothing at all, and a
|
|
390
|
+
* reader who wanted the list had to find the text box. Anything drawn inside
|
|
391
|
+
* the field is the field.
|
|
392
|
+
*
|
|
393
|
+
* A tag is the exception, because a tag is its own control: its dismiss
|
|
394
|
+
* removes a choice, and opening the list on the way past would leave the
|
|
395
|
+
* reader looking at a panel they did not ask for. Read from the composed
|
|
396
|
+
* path, since the press lands on a button inside the tag's shadow root and
|
|
397
|
+
* the event has retargeted by the time it arrives here.
|
|
398
|
+
*
|
|
399
|
+
* It only ever opens. Closing is Escape, a choice, or leaving, which is what
|
|
400
|
+
* the input already did and what a text field wants: a control that closed
|
|
401
|
+
* when you clicked into the middle of your own typing would be unusable.
|
|
402
|
+
*/
|
|
403
|
+
this.handleFieldClick = (event) => {
|
|
404
|
+
if (this.isDisabled) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (event
|
|
408
|
+
.composedPath()
|
|
409
|
+
.some((node) => node instanceof HTMLElement && node.localName === 'helix-tag')) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.renderRoot.querySelector('[part="control"]')?.focus();
|
|
413
|
+
if (!this.open) {
|
|
414
|
+
this.requestOpen(true);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
/** One tag asking to be taken out of the set. */
|
|
418
|
+
this.handleDismiss = (event) => {
|
|
419
|
+
event.stopPropagation();
|
|
420
|
+
const tag = event.currentTarget;
|
|
421
|
+
this.drop(tag.dataset.value ?? '');
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Which option is chosen, as an index, or `-1` where none is.
|
|
426
|
+
*
|
|
427
|
+
* Read off the listbox this renders, which owns the options and is where the
|
|
428
|
+
* same two properties are implemented. Delegating rather than recomputing is
|
|
429
|
+
* what keeps a composed control and the control inside it from answering
|
|
430
|
+
* differently: they are one chooser to a consumer.
|
|
431
|
+
*/
|
|
432
|
+
get selectedIndex() {
|
|
433
|
+
return this.listbox?.selectedIndex ?? -1;
|
|
434
|
+
}
|
|
435
|
+
set selectedIndex(at) {
|
|
436
|
+
const listbox = this.listbox;
|
|
437
|
+
if (listbox) {
|
|
438
|
+
listbox.selectedIndex = at;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/** Which kind of chooser this is, in the platform's two words. */
|
|
442
|
+
get type() {
|
|
443
|
+
return this.multiple ? 'select-multiple' : 'select-one';
|
|
444
|
+
}
|
|
445
|
+
get listbox() {
|
|
446
|
+
return this.renderRoot?.querySelector('[part="listbox"]') ?? null;
|
|
447
|
+
}
|
|
448
|
+
/** The values chosen, in the order they were chosen. */
|
|
449
|
+
get values() {
|
|
450
|
+
return this.value === '' ? [] : this.value.split(',');
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The chosen options themselves, for their words.
|
|
454
|
+
*
|
|
455
|
+
* Read off the options rather than stored, because the words are the
|
|
456
|
+
* consumer's content and a copy of them goes stale the moment they change it.
|
|
457
|
+
* Select records the same.
|
|
458
|
+
*
|
|
459
|
+
* In the value's own order, which is the order they were chosen. It has to
|
|
460
|
+
* be: a created answer is not in the list at all, so there is no position in
|
|
461
|
+
* the list to give it. Neither order ever reshuffles what is already there,
|
|
462
|
+
* so a tag does not move out from under the pointer about to remove it.
|
|
463
|
+
*/
|
|
464
|
+
get chosen() {
|
|
465
|
+
const options = this.listbox?.options ?? [];
|
|
466
|
+
/* Nothing at all is not the same as nothing matching.
|
|
467
|
+
*
|
|
468
|
+
* On the server there are no options to look in, because they are light DOM
|
|
469
|
+
* children the server cannot see, and before the slot is assigned there are
|
|
470
|
+
* none either. Falling back there would put the raw value on screen for one
|
|
471
|
+
* paint and then swap it for the option's words, which reads as the field
|
|
472
|
+
* correcting a mistake. With options present, a value none of them carries
|
|
473
|
+
* is a created one, and showing it is the only way to show it at all. */
|
|
474
|
+
const known = options.length > 0;
|
|
475
|
+
const words = new Map(options.map((option) => [option.value, option.text]));
|
|
476
|
+
return this.values
|
|
477
|
+
.filter((value) => known || words.has(value))
|
|
478
|
+
.map((value) => ({ text: words.get(value) ?? value, value }));
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Whether the filter has left anything to choose.
|
|
482
|
+
*
|
|
483
|
+
* Read off the options rather than asked of the listbox, which keeps its own
|
|
484
|
+
* answer to this private. Both come from the same place: `hidden` is what the
|
|
485
|
+
* filter writes onto an option, and it is public because hiding one by hand
|
|
486
|
+
* is a thing a consumer may do.
|
|
487
|
+
*/
|
|
488
|
+
get showing() {
|
|
489
|
+
return (this.listbox?.options ?? []).some((option) => !option.hidden);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* What the create row would add, or nothing where there is nothing to add.
|
|
493
|
+
*
|
|
494
|
+
* Empty where the words already name an option exactly, case aside: offering
|
|
495
|
+
* to add "Rust" under a list showing "Rust" is offering the reader a second
|
|
496
|
+
* copy of what they are looking at.
|
|
497
|
+
*/
|
|
498
|
+
get creation() {
|
|
499
|
+
const typed = this.typed.trim();
|
|
500
|
+
if (!this.creatable || typed === '') {
|
|
501
|
+
return '';
|
|
502
|
+
}
|
|
503
|
+
const exists = (this.listbox?.options ?? []).some((option) => option.text.trim().toLowerCase() === typed.toLowerCase());
|
|
504
|
+
return exists ? '' : typed;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Takes what the reader typed as an answer, once anyone listening allows it.
|
|
508
|
+
*
|
|
509
|
+
* Added to the set with `multiple` and replacing it without, which is the
|
|
510
|
+
* same rule choosing an option follows: what differs about a created answer
|
|
511
|
+
* is where it came from, not how many the field holds.
|
|
512
|
+
*/
|
|
513
|
+
create() {
|
|
514
|
+
const wanted = this.creation;
|
|
515
|
+
if (wanted === '') {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (!this.dispatchEvent(new HelixCreateEvent({ value: wanted }))) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const next = this.multiple ? [...this.values, wanted].join(',') : wanted;
|
|
522
|
+
if (!this.requestValue(next)) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
this.typed = '';
|
|
526
|
+
this.suggestion = '';
|
|
527
|
+
this.listbox?.requestFilter('');
|
|
528
|
+
if (!this.multiple) {
|
|
529
|
+
this.close();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/** The choices that get a tag, which is all of them until `maxTags` says not. */
|
|
533
|
+
get shown() {
|
|
534
|
+
const held = this.chosen;
|
|
535
|
+
return this.maxTags > 0 ? held.slice(0, this.maxTags) : held;
|
|
536
|
+
}
|
|
537
|
+
/** How many are held beyond the ones shown. */
|
|
538
|
+
get restCount() {
|
|
539
|
+
return Math.max(0, this.chosen.length - this.shown.length);
|
|
540
|
+
}
|
|
541
|
+
/** What a single-choice field shows at rest, which the tags replace. */
|
|
542
|
+
get word() {
|
|
543
|
+
return this.chosen[0]?.text ?? '';
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Takes one value out of the set, which is what a tag's remove control does.
|
|
547
|
+
*
|
|
548
|
+
* `drop` rather than `remove`, which every element already has: `Element`
|
|
549
|
+
* defines `remove()` and a method of that name here would be overriding it,
|
|
550
|
+
* so `combobox.remove()` would stop taking the combobox out of the document.
|
|
551
|
+
*/
|
|
552
|
+
drop(value) {
|
|
553
|
+
this.requestValue(this.values.filter((one) => one !== value).join(','));
|
|
554
|
+
}
|
|
555
|
+
close() {
|
|
556
|
+
if (!this.open) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
this.requestOpen(false);
|
|
560
|
+
this.renderRoot.querySelector('[part="control"]')?.focus();
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* The words of the first option the typing begins, or nothing.
|
|
564
|
+
*
|
|
565
|
+
* Case-insensitively matched and returned in the option's own case, so a
|
|
566
|
+
* reader typing "ru" is offered "Rust" rather than "rust": the completion is
|
|
567
|
+
* a name from the list, and correcting the reader's capitals to match it is
|
|
568
|
+
* the whole point of showing it.
|
|
569
|
+
*/
|
|
570
|
+
completionFor(typed) {
|
|
571
|
+
if (typed === '') {
|
|
572
|
+
return '';
|
|
573
|
+
}
|
|
574
|
+
const needle = typed.toLowerCase();
|
|
575
|
+
const match = (this.listbox?.options ?? []).find((option) => !option.hidden && !option.disabled && option.text.toLowerCase().startsWith(needle));
|
|
576
|
+
return match && match.text.length > typed.length ? match.text : '';
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Reports the answer and the verdict on it.
|
|
580
|
+
*
|
|
581
|
+
* The value rather than the typed text, which is the whole point of the
|
|
582
|
+
* component: a form that received "jav" got the filter, not an option.
|
|
583
|
+
*/
|
|
584
|
+
sync() {
|
|
585
|
+
this.internals.setFormValue(this.value === '' ? null : this.value);
|
|
586
|
+
this.reportRequired(this.value === '', 'Choose one to continue.', 'control');
|
|
587
|
+
}
|
|
588
|
+
willUpdate(changed) {
|
|
589
|
+
super.willUpdate(changed);
|
|
590
|
+
this.internals.ariaExpanded = this.open ? 'true' : 'false';
|
|
591
|
+
}
|
|
592
|
+
updated(changed) {
|
|
593
|
+
super.updated?.(changed);
|
|
594
|
+
this.sync();
|
|
595
|
+
/* After the render, not during it: the input's value is a binding, so
|
|
596
|
+
selecting a range of it before Lit has written it selects a range of the
|
|
597
|
+
previous value.
|
|
598
|
+
|
|
599
|
+
**And written here rather than left to the binding, because the binding
|
|
600
|
+
stops writing.** Lit dirty-checks a property binding against the value it
|
|
601
|
+
last wrote, not against the DOM, and typing changes the DOM underneath
|
|
602
|
+
it. Type `r` into a list holding Ruby and the completion is `Ruby`, so
|
|
603
|
+
the field is written `Ruby` with `uby` selected. Type `u` and the field
|
|
604
|
+
is already `Ru` because the browser replaced the selection, and the
|
|
605
|
+
completion is *still* `Ruby`: unchanged, so Lit writes nothing and
|
|
606
|
+
`changed.has('suggestion')` is false, so nothing is selected either. The
|
|
607
|
+
completion appeared on the first character and never again, which is
|
|
608
|
+
exactly how it was reported.
|
|
609
|
+
|
|
610
|
+
Reconciled against what the input actually holds, which is the only thing
|
|
611
|
+
that knows. */
|
|
612
|
+
const control = this.renderRoot.querySelector('[part="control"]');
|
|
613
|
+
/* The same expression the template binds, because this has to agree with it
|
|
614
|
+
in both directions. Writing only when there is a completion put the field
|
|
615
|
+
right while one was showing and left it stale afterwards: this reconcile
|
|
616
|
+
writes behind Lit's back, so Lit's own dirty-check then believes the DOM
|
|
617
|
+
already holds whatever it last wrote and declines to correct it. A field
|
|
618
|
+
cleared on close kept the last completion for exactly that reason. */
|
|
619
|
+
const wanted = this.multiple || this.open ? this.suggestion || this.typed : this.word;
|
|
620
|
+
if (control && control.value !== wanted) {
|
|
621
|
+
control.value = wanted;
|
|
622
|
+
}
|
|
623
|
+
if (control && this.suggestion !== '') {
|
|
624
|
+
control.setSelectionRange(this.typed.length, this.suggestion.length);
|
|
625
|
+
}
|
|
626
|
+
if (this.open) {
|
|
627
|
+
this.panel.show();
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
this.panel.hide();
|
|
631
|
+
/* And the filter goes with it, for the reason the class note records: a
|
|
632
|
+
control reopened still narrowed is one whose second use starts from the
|
|
633
|
+
first one's leftovers.
|
|
634
|
+
|
|
635
|
+
All three of them, which is the fix. Only the listbox's copy was
|
|
636
|
+
cleared here, so `typed` survived a dismissal: the field looked empty
|
|
637
|
+
because a closed single-choice field shows the chosen words instead,
|
|
638
|
+
and the moment it reopened the old search was back in it with the list
|
|
639
|
+
behind it showing everything. Measured by pressing Escape on "ru" and
|
|
640
|
+
opening it again. */
|
|
641
|
+
this.typed = '';
|
|
642
|
+
this.suggestion = '';
|
|
643
|
+
this.listbox?.requestFilter('');
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* The list the field filters, behind a seam the styled tier can move.
|
|
648
|
+
*
|
|
649
|
+
* A method rather than a fragment inline, so a tier above can render its own,
|
|
650
|
+
* wrap this one, pass it a property the base does not know about, or leave it
|
|
651
|
+
* out, without reimplementing the whole template to do any of them. The tag is
|
|
652
|
+
* interpolated rather than written, so `tier` resolves it: this renders the
|
|
653
|
+
* headless element, and a styled subclass that sets `tier` renders the styled one
|
|
654
|
+
* from the same template.
|
|
655
|
+
*/
|
|
656
|
+
renderListbox() {
|
|
657
|
+
return html `<${this.tag('listbox')}
|
|
658
|
+
part="listbox"
|
|
659
|
+
id="listbox"
|
|
660
|
+
exportparts="empty: listbox-empty, feedback: listbox-feedback, filter-icon: listbox-filter-icon,
|
|
661
|
+
filter-mark: listbox-filter-mark, hint: listbox-hint, items: listbox-items,
|
|
662
|
+
label: listbox-label, loading: listbox-loading, search: listbox-search,
|
|
663
|
+
search-field: listbox-search-field"
|
|
664
|
+
aria-label=${this.listLabel}
|
|
665
|
+
empty-text=${this.emptyText}
|
|
666
|
+
loading-text=${this.loadingText}
|
|
667
|
+
?loading=${this.loading}
|
|
668
|
+
?multiple=${this.multiple}
|
|
669
|
+
.value=${this.value}
|
|
670
|
+
@helix-before-change=${this.handleChange}
|
|
671
|
+
@change=${this.stopInner}
|
|
672
|
+
@input=${this.stopInner}
|
|
673
|
+
>
|
|
674
|
+
<!-- The field's words are read off the options, and on the first
|
|
675
|
+
render there are none: the slot has not been assigned yet, so a
|
|
676
|
+
combobox with a value came up empty. Assignment changes no
|
|
677
|
+
property, so nothing else would schedule the update that fixes
|
|
678
|
+
it. Select was fixed for exactly this. -->
|
|
679
|
+
<slot @slotchange=${() => this.requestUpdate()}></slot>
|
|
680
|
+
</${this.tag('listbox')}>`;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* One chosen option, as a tag, behind a seam the styled tier can move.
|
|
684
|
+
*
|
|
685
|
+
* A method rather than a fragment inline, so a tier above can render its own,
|
|
686
|
+
* wrap this one, pass it a property the base does not know about, or leave it
|
|
687
|
+
* out, without reimplementing the whole template to do any of them. The tag is
|
|
688
|
+
* interpolated rather than written, so `tier` resolves it: this renders the
|
|
689
|
+
* headless element, and a styled subclass that sets `tier` renders the styled one
|
|
690
|
+
* from the same template.
|
|
691
|
+
*/
|
|
692
|
+
renderTag(option) {
|
|
693
|
+
return html `<${this.tag('tag')}
|
|
694
|
+
part="tag"
|
|
695
|
+
exportparts="dismiss: tag-dismiss, dismiss-mark: tag-dismiss-mark, prefix: tag-prefix"
|
|
696
|
+
data-value=${option.value}
|
|
697
|
+
dismiss-label=${this.removeLabel}
|
|
698
|
+
?dismissible=${!this.isDisabled}
|
|
699
|
+
@helix-dismiss=${this.handleDismiss}
|
|
700
|
+
>${option.text}</${this.tag('tag')}
|
|
701
|
+
>`;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* The caret saying the field opens, behind a seam.
|
|
705
|
+
*
|
|
706
|
+
* Slot fallback, so a consumer replaces it by slotting; and a method, so the
|
|
707
|
+
* tier above replaces it by overriding. Those answer different people: the
|
|
708
|
+
* slot is for the page and the method is for whoever is building the styled
|
|
709
|
+
* layer, and a fallback with only the first is one a tier can restyle and
|
|
710
|
+
* cannot change.
|
|
711
|
+
*/
|
|
712
|
+
renderIndicator() {
|
|
713
|
+
return html `<slot name="indicator">
|
|
714
|
+
<svg part="indicator" fill="currentColor" viewBox=${caretDown.viewBox} aria-hidden="true">
|
|
715
|
+
${unsafeSVG(caretDown.content)}
|
|
716
|
+
</svg>
|
|
717
|
+
</slot>`;
|
|
718
|
+
}
|
|
719
|
+
render() {
|
|
720
|
+
return html `${this.renderLabel()}
|
|
721
|
+
<div part="field" @click=${this.handleFieldClick}>
|
|
722
|
+
<slot name="start"></slot>
|
|
723
|
+
${this.multiple && this.chosen.length > 0
|
|
724
|
+
? html `<span part="tags">
|
|
725
|
+
${this.shown.map((option) => this.renderTag(option))}
|
|
726
|
+
${this.restCount > 0
|
|
727
|
+
? html `<span part="rest">${`+${this.restCount}`}</span>`
|
|
728
|
+
: nothing}
|
|
729
|
+
</span>`
|
|
730
|
+
: nothing}
|
|
731
|
+
<input
|
|
732
|
+
part="control"
|
|
733
|
+
id="control"
|
|
734
|
+
type="text"
|
|
735
|
+
role="combobox"
|
|
736
|
+
autocomplete=${this.autocomplete || nothing}
|
|
737
|
+
spellcheck="false"
|
|
738
|
+
aria-controls="listbox"
|
|
739
|
+
aria-autocomplete=${this.suggest ? 'both' : 'list'}
|
|
740
|
+
aria-expanded=${this.open ? 'true' : 'false'}
|
|
741
|
+
aria-describedby=${this.describedBy ?? nothing}
|
|
742
|
+
aria-multiselectable=${this.multiple ? 'true' : nothing}
|
|
743
|
+
placeholder=${(this.chosen.length > 0 ? '' : this.placeholder) || nothing}
|
|
744
|
+
?disabled=${this.isDisabled}
|
|
745
|
+
.value=${this.multiple || this.open ? this.suggestion || this.typed : this.word}
|
|
746
|
+
@input=${this.handleInput}
|
|
747
|
+
@keydown=${this.handleKeydown}
|
|
748
|
+
/>
|
|
749
|
+
<slot name="end"></slot>
|
|
750
|
+
${this.renderIndicator()}
|
|
751
|
+
</div>
|
|
752
|
+
<div part="surface" popover="manual" ?hidden=${!this.open}>
|
|
753
|
+
${this.renderListbox()}
|
|
754
|
+
${this.creation === ''
|
|
755
|
+
? nothing
|
|
756
|
+
: html `<button part="create" type="button" @click=${() => this.create()}>
|
|
757
|
+
${this.createLabel} "${this.creation}"
|
|
758
|
+
</button>`}
|
|
759
|
+
</div>
|
|
760
|
+
${this.renderHint()}${this.renderFeedback()}`;
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
__decorate([
|
|
764
|
+
property()
|
|
765
|
+
], HelixHeadlessCombobox.prototype, "autocomplete", void 0);
|
|
766
|
+
__decorate([
|
|
767
|
+
property()
|
|
768
|
+
], HelixHeadlessCombobox.prototype, "placeholder", void 0);
|
|
769
|
+
__decorate([
|
|
770
|
+
property({ type: Boolean })
|
|
771
|
+
], HelixHeadlessCombobox.prototype, "multiple", void 0);
|
|
772
|
+
__decorate([
|
|
773
|
+
property({ type: Boolean })
|
|
774
|
+
], HelixHeadlessCombobox.prototype, "creatable", void 0);
|
|
775
|
+
__decorate([
|
|
776
|
+
property({ type: Boolean })
|
|
777
|
+
], HelixHeadlessCombobox.prototype, "suggest", void 0);
|
|
778
|
+
__decorate([
|
|
779
|
+
property({ attribute: 'create-label' })
|
|
780
|
+
], HelixHeadlessCombobox.prototype, "createLabel", void 0);
|
|
781
|
+
__decorate([
|
|
782
|
+
property({ attribute: 'remove-label' })
|
|
783
|
+
], HelixHeadlessCombobox.prototype, "removeLabel", void 0);
|
|
784
|
+
__decorate([
|
|
785
|
+
property({ attribute: 'max-tags', type: Number })
|
|
786
|
+
], HelixHeadlessCombobox.prototype, "maxTags", void 0);
|
|
787
|
+
__decorate([
|
|
788
|
+
property({ attribute: 'list-label' })
|
|
789
|
+
], HelixHeadlessCombobox.prototype, "listLabel", void 0);
|
|
790
|
+
__decorate([
|
|
791
|
+
property({ reflect: true, type: Boolean })
|
|
792
|
+
], HelixHeadlessCombobox.prototype, "loading", void 0);
|
|
793
|
+
__decorate([
|
|
794
|
+
property({ attribute: 'loading-text' })
|
|
795
|
+
], HelixHeadlessCombobox.prototype, "loadingText", void 0);
|
|
796
|
+
__decorate([
|
|
797
|
+
property({ attribute: 'empty-text' })
|
|
798
|
+
], HelixHeadlessCombobox.prototype, "emptyText", void 0);
|
|
799
|
+
__decorate([
|
|
800
|
+
state()
|
|
801
|
+
], HelixHeadlessCombobox.prototype, "typed", void 0);
|
|
802
|
+
__decorate([
|
|
803
|
+
state()
|
|
804
|
+
], HelixHeadlessCombobox.prototype, "suggestion", void 0);
|
|
805
|
+
HelixHeadlessCombobox = __decorate([
|
|
806
|
+
customElement('helix-headless-combobox')
|
|
807
|
+
], HelixHeadlessCombobox);
|
|
808
|
+
export { HelixHeadlessCombobox };
|