@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,890 @@
|
|
|
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, DisabledCascadeController, HelixFilterEvent, HelixFormElement, hostAria, MixinCollection, MixinControllerValue, MixinField, requestChange, scrollItemIntoView, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { magnifyingGlass } from '@fusion.dev/helix-icons';
|
|
9
|
+
import { html, nothing } from 'lit';
|
|
10
|
+
import { property, state } from 'lit/decorators.js';
|
|
11
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
+
import { HelixHeadlessOptionGroup } from '../../option/group/option-group.js';
|
|
13
|
+
import { HelixHeadlessOption } from '../../option/root/option-root.js';
|
|
14
|
+
/** The keys that move within a list, which is every key that moves at all. */
|
|
15
|
+
const MOVES = new Set(['ArrowDown', 'ArrowUp', 'End', 'Home']);
|
|
16
|
+
/**
|
|
17
|
+
* The ranges each filtering list has matched, so the union can be rebuilt when
|
|
18
|
+
* any one of them changes.
|
|
19
|
+
*
|
|
20
|
+
* `CSS.highlights` is one registry for the whole document, keyed by name, so a
|
|
21
|
+
* second list writing under the same name would erase the first one's marks.
|
|
22
|
+
* Keyed by element and rebuilt from every entry, two filtered lists on a page
|
|
23
|
+
* both stay marked. A `Map` rather than a `WeakMap` because the union has to be
|
|
24
|
+
* walked and a `WeakMap` cannot be, which is why a list takes itself out on
|
|
25
|
+
* disconnect.
|
|
26
|
+
*/
|
|
27
|
+
const matched = new Map();
|
|
28
|
+
/** What the marks are called in `CSS.highlights`, and in `preview.css`. */
|
|
29
|
+
const HIGHLIGHT = 'helix-filter-match';
|
|
30
|
+
/*
|
|
31
|
+
* Marks what the filter matched, without touching what it matched it in.
|
|
32
|
+
*
|
|
33
|
+
* The words are the consumer's own light DOM, slotted through as many as two
|
|
34
|
+
* shadow roots, and wrapping the matched part in a `<mark>` would mean this
|
|
35
|
+
* element rewriting their content: markup they wrote would come back changed.
|
|
36
|
+
* The Custom Highlight API paints a range instead. Nothing is inserted and
|
|
37
|
+
* nothing is removed, so an option holding a link or an icon is marked as
|
|
38
|
+
* readily as one holding a bare word.
|
|
39
|
+
*
|
|
40
|
+
* The rule that paints it lives in `preview.css` rather than in a component's
|
|
41
|
+
* styles, which is not a choice either: a highlight is styled from the tree its
|
|
42
|
+
* text lives in, and that text lives in the consumer's document.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Whether this environment can paint a highlight at all.
|
|
46
|
+
*
|
|
47
|
+
* Asked before the ranges are built rather than after. Walking every shown
|
|
48
|
+
* option's text nodes to hand the result to an API that is not there is work
|
|
49
|
+
* with no output, and it is not free: a server, a test environment and an older
|
|
50
|
+
* browser all take that path, and the DOM implementations they use are not the
|
|
51
|
+
* one this was measured against.
|
|
52
|
+
*/
|
|
53
|
+
const paintable = () => typeof CSS !== 'undefined' && 'highlights' in CSS && typeof Highlight !== 'undefined';
|
|
54
|
+
const paintMatches = () => {
|
|
55
|
+
if (!paintable()) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const all = [...matched.values()].flat();
|
|
59
|
+
if (all.length === 0) {
|
|
60
|
+
CSS.highlights.delete(HIGHLIGHT);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
CSS.highlights.set(HIGHLIGHT, new Highlight(...all));
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Where a needle sits inside an element, as a range over its text nodes.
|
|
67
|
+
*
|
|
68
|
+
* Walked rather than measured off `textContent`, because the words may be split
|
|
69
|
+
* across several nodes and wrapped in elements of the consumer's, and a match
|
|
70
|
+
* can begin in one node and end in another. The offsets are mapped back onto
|
|
71
|
+
* the nodes they came from, so the range covers the real text either way.
|
|
72
|
+
*/
|
|
73
|
+
const rangeFor = (root, needle) => {
|
|
74
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
75
|
+
const nodes = [];
|
|
76
|
+
let whole = '';
|
|
77
|
+
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
|
|
78
|
+
nodes.push(node);
|
|
79
|
+
whole += node.nodeValue ?? '';
|
|
80
|
+
}
|
|
81
|
+
const at = whole.toLowerCase().indexOf(needle);
|
|
82
|
+
if (at < 0) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const range = document.createRange();
|
|
86
|
+
let seen = 0;
|
|
87
|
+
let started = false;
|
|
88
|
+
for (const node of nodes) {
|
|
89
|
+
const length = node.nodeValue?.length ?? 0;
|
|
90
|
+
if (!started && at < seen + length) {
|
|
91
|
+
range.setStart(node, at - seen);
|
|
92
|
+
started = true;
|
|
93
|
+
}
|
|
94
|
+
if (started && at + needle.length <= seen + length) {
|
|
95
|
+
range.setEnd(node, at + needle.length - seen);
|
|
96
|
+
return [range];
|
|
97
|
+
}
|
|
98
|
+
seen += length;
|
|
99
|
+
}
|
|
100
|
+
return [];
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* @summary A list of options, one or several of which are the answer.
|
|
104
|
+
*
|
|
105
|
+
* @description
|
|
106
|
+
* **The listbox is the control and the options are its vocabulary.** A Select
|
|
107
|
+
* is this with a trigger in front of it, a Combobox is this with a text input,
|
|
108
|
+
* and a command palette is this again: they differ in what opens the list and
|
|
109
|
+
* what filters it, not in what the list is. Building it as its own family is
|
|
110
|
+
* what stops each of those reimplementing the keyboard and the filtering.
|
|
111
|
+
*
|
|
112
|
+
* **`value` is comma separated when `multiple` is set**, the way
|
|
113
|
+
* `Checkbox.Group`'s is. It stays a string because that is what a form submits
|
|
114
|
+
* and what `MixinControllerValue` holds; an array property would be a second
|
|
115
|
+
* shape for the same fact, and the two would disagree the first time one was
|
|
116
|
+
* set without the other.
|
|
117
|
+
*
|
|
118
|
+
* **Chosen and active are two different things.** Chosen is the answer; active
|
|
119
|
+
* is where the keyboard is. In a single-select list they move together, which
|
|
120
|
+
* is the radio group pattern and is what a reader expects from arrow keys. With
|
|
121
|
+
* `multiple` they come apart: the arrows move without choosing and Space
|
|
122
|
+
* chooses, because a reader passing over four options on the way to the fifth
|
|
123
|
+
* did not mean to pick all five.
|
|
124
|
+
*
|
|
125
|
+
* **This does not use `KeyboardNavigationController`, and the reason given here
|
|
126
|
+
* was the wrong one.** It said the controller moves and chooses in one act by
|
|
127
|
+
* design, so half of this component needed that and half the opposite. The
|
|
128
|
+
* controller does not work that way: it takes `origin: 'focus' | 'selection'`,
|
|
129
|
+
* and `focus` exists precisely so a reader can arrow without choosing, which is
|
|
130
|
+
* what `Tabs` uses in `manual` mode. `focusItem()` and `select()` are separate
|
|
131
|
+
* members of `NavigableItem`. The incompatibility named above is one the
|
|
132
|
+
* controller had already solved.
|
|
133
|
+
*
|
|
134
|
+
* The real one is where focus lives. `NavigableItem.focusItem()` moves DOM
|
|
135
|
+
* focus to the item, because the controller's model is a roving tab stop. A
|
|
136
|
+
* filterable list cannot do that: focus has to stay in the field or typing
|
|
137
|
+
* stops, so what moves is a virtual cursor and `aria-activedescendant` is what
|
|
138
|
+
* says where it is. Those two disagree about the one thing neither can
|
|
139
|
+
* abstract, and no flag reconciles them.
|
|
140
|
+
*
|
|
141
|
+
* Worth keeping the distinction sharp, because the old wording invites the
|
|
142
|
+
* wrong fix: someone reading it would add `origin` to the controller, find it
|
|
143
|
+
* already there, and conclude this component could adopt it.
|
|
144
|
+
*
|
|
145
|
+
* **Filtering hides rather than removes.** An option the filter has hidden is
|
|
146
|
+
* still the consumer's element in the consumer's order, so a filter that clears
|
|
147
|
+
* puts the list back exactly as it was with nothing re-rendered. What counts as
|
|
148
|
+
* a match is a plain case-insensitive substring, and a consumer wanting more
|
|
149
|
+
* hides options itself: `filter` is a convenience over the same `hidden` the
|
|
150
|
+
* component uses.
|
|
151
|
+
*
|
|
152
|
+
* @since 0.1.0
|
|
153
|
+
* @status experimental
|
|
154
|
+
* @category forms
|
|
155
|
+
* @aliases Options, Select list
|
|
156
|
+
* @documentation https://helix.fusion.dev/components/listbox
|
|
157
|
+
*
|
|
158
|
+
* @slot - The options, and any groups of them.
|
|
159
|
+
* @slot hint - Richer than a string.
|
|
160
|
+
* @slot label - Richer than a string.
|
|
161
|
+
*
|
|
162
|
+
* @event {Event} change - Announced once the value has moved.
|
|
163
|
+
* @event {HelixBeforeChangeEvent} helix-before-change - Fired when the answer changes, and cancelable.
|
|
164
|
+
* @event {HelixFilterEvent} helix-filter - Fired before the list narrows, and cancelable.
|
|
165
|
+
* @event {Event} input - Announced once the value has moved.
|
|
166
|
+
*
|
|
167
|
+
* @csspart empty - Shown when a filter has hidden everything.
|
|
168
|
+
* @csspart feedback - What is wrong with it.
|
|
169
|
+
* @csspart filter-icon - The mark beside that field.
|
|
170
|
+
* @csspart filter-mark - The glyph beside the filter field, where none is slotted.
|
|
171
|
+
* @csspart hint - Help under the list.
|
|
172
|
+
* @csspart items - The list itself.
|
|
173
|
+
* @csspart label - The question, over the list.
|
|
174
|
+
* @csspart loading - Shown while the options are still on their way.
|
|
175
|
+
* @csspart search - The region holding the field that narrows the list.
|
|
176
|
+
* @csspart search-field - The field itself.
|
|
177
|
+
*/
|
|
178
|
+
let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(MixinControllerValue(MixinField(HelixFormElement))) {
|
|
179
|
+
constructor() {
|
|
180
|
+
super(...arguments);
|
|
181
|
+
/* A consumer's `aria-describedby` names ids in their own tree and the control
|
|
182
|
+
is in this shadow root, so the reference has to be carried down as an
|
|
183
|
+
element rather than copied as an id. See `AriaForwardController`: measured
|
|
184
|
+
in Chrome's own tree, the host held the description and the control the
|
|
185
|
+
reader is actually on was exposed without it. */
|
|
186
|
+
this.aria = new AriaForwardController(this, {
|
|
187
|
+
target: () => this.renderRoot?.querySelector('[part="items"]') ?? null,
|
|
188
|
+
});
|
|
189
|
+
/**
|
|
190
|
+
* What the browser may autofill in the text field, as a platform token.
|
|
191
|
+
*
|
|
192
|
+
* **Defaults to `off`, which is not the platform's default and is the
|
|
193
|
+
* point.** The field filters a list rather than collecting a value, so a
|
|
194
|
+
* browser offering to fill it with a saved address is competing for the same
|
|
195
|
+
* keystrokes as the listbox. A consumer who wants autofill can still ask.
|
|
196
|
+
*/
|
|
197
|
+
this.autocomplete = 'off';
|
|
198
|
+
/**
|
|
199
|
+
* What to start narrowed to, where nothing declared it.
|
|
200
|
+
*/
|
|
201
|
+
this.defaultFilter = '';
|
|
202
|
+
/** What the list is narrowed to, whoever set it. */
|
|
203
|
+
this.filterState = undefined;
|
|
204
|
+
/**
|
|
205
|
+
* Whether the list renders a field for narrowing itself.
|
|
206
|
+
*
|
|
207
|
+
* The field lives here rather than in whatever frames the list, because the
|
|
208
|
+
* filtering already does: `filter`, the empty state and the keyboard are all
|
|
209
|
+
* this component's. A select that drew its own search box above a listbox
|
|
210
|
+
* would be writing the same field again, and so would a combobox, and so
|
|
211
|
+
* would a command palette, which is the fault the three-family split exists
|
|
212
|
+
* to prevent.
|
|
213
|
+
*/
|
|
214
|
+
this.filterable = false;
|
|
215
|
+
/**
|
|
216
|
+
* Placeholder for the field that narrows the list.
|
|
217
|
+
*/
|
|
218
|
+
this.filterLabel = 'Filter';
|
|
219
|
+
/**
|
|
220
|
+
* Whether more than one option can be the answer.
|
|
221
|
+
*/
|
|
222
|
+
this.multiple = false;
|
|
223
|
+
/**
|
|
224
|
+
* What is shown when a filter has hidden every option.
|
|
225
|
+
*
|
|
226
|
+
* From `MixinCollection`, which every collection in the library now shares,
|
|
227
|
+
* and re-declared here only to keep this list's own wording: "No matches" is
|
|
228
|
+
* about a filter, and the mixin's "Nothing to show" is about a list that was
|
|
229
|
+
* never given anything.
|
|
230
|
+
*/
|
|
231
|
+
this.emptyText = 'No matches';
|
|
232
|
+
/**
|
|
233
|
+
* Which option the keyboard is on, which is not what is chosen.
|
|
234
|
+
*/
|
|
235
|
+
this.activeValue = '';
|
|
236
|
+
/**
|
|
237
|
+
* Whether the key being handled was forwarded rather than typed into this.
|
|
238
|
+
*
|
|
239
|
+
* Plain state rather than reactive: it is set and cleared inside one
|
|
240
|
+
* synchronous call, so nothing renders while it is true and nothing outside
|
|
241
|
+
* can observe it.
|
|
242
|
+
*/
|
|
243
|
+
this.forwarded = false;
|
|
244
|
+
/**
|
|
245
|
+
* Whether anything survived the filter, kept as state rather than counted at
|
|
246
|
+
* render time.
|
|
247
|
+
*
|
|
248
|
+
* Counting during render reads the hiding from the *previous* pass, because
|
|
249
|
+
* hiding happens in `updated` against options that are the consumer's
|
|
250
|
+
* elements rather than this component's markup. So a filter matching nothing
|
|
251
|
+
* rendered the full list, hid every option, and left an empty box with no
|
|
252
|
+
* message in it: the count was right one pass too late. Written to state
|
|
253
|
+
* instead, which schedules the one further render that shows it.
|
|
254
|
+
*/
|
|
255
|
+
this.hasMatches = true;
|
|
256
|
+
/**
|
|
257
|
+
* Whether the options have been looked at yet.
|
|
258
|
+
*
|
|
259
|
+
* Emptiness is a fact about light DOM children, and the server has none to
|
|
260
|
+
* count: without this the list shipped "Nothing to show" over markup that was
|
|
261
|
+
* about to have options in it, which is the one thing an empty state must
|
|
262
|
+
* never do. `place` runs from `updated`, which the server never reaches, so
|
|
263
|
+
* this is false exactly when the answer is unknown.
|
|
264
|
+
*/
|
|
265
|
+
this.counted = false;
|
|
266
|
+
/* Stated once, and the group never thinks about it again. See the
|
|
267
|
+
controller: the cascade was four hand calls from four different
|
|
268
|
+
methods, so which paths reached it differed by component and a fifth
|
|
269
|
+
group would have got none at all. */
|
|
270
|
+
this.cascade = new DisabledCascadeController(this, {
|
|
271
|
+
items: () => this.options,
|
|
272
|
+
});
|
|
273
|
+
this.handleSelection = (event) => {
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
this.activeValue = event.detail.value;
|
|
276
|
+
this.choose(event.detail.value);
|
|
277
|
+
};
|
|
278
|
+
/**
|
|
279
|
+
* Moves the keyboard, and chooses with it only where that is the pattern.
|
|
280
|
+
*
|
|
281
|
+
* Space and Enter are how a multi-select list chooses, and they are also how
|
|
282
|
+
* a single-select one confirms what the arrows already did, so they act
|
|
283
|
+
* either way rather than being guarded on `multiple`.
|
|
284
|
+
*/
|
|
285
|
+
this.handleKeydown = (event) => {
|
|
286
|
+
const shown = this.shown.filter((option) => !option.disabled);
|
|
287
|
+
if (shown.length === 0) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
if (this.activeValue !== '') {
|
|
293
|
+
this.choose(this.activeValue);
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
/* The list's own movement keys, plus Enter, which is choosing rather than
|
|
298
|
+
moving and is still the list's: a reader who has narrowed to one option
|
|
299
|
+
expects Enter to take it without leaving the field first. */
|
|
300
|
+
if (!MOVES.has(event.key) && event.key !== 'Enter') {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
event.preventDefault();
|
|
304
|
+
const from = this.currentValue;
|
|
305
|
+
const at = shown.findIndex((option) => option.value === from);
|
|
306
|
+
let next = at;
|
|
307
|
+
if (event.key === 'ArrowDown') {
|
|
308
|
+
next = at < 0 ? 0 : (at + 1) % shown.length;
|
|
309
|
+
}
|
|
310
|
+
else if (event.key === 'ArrowUp') {
|
|
311
|
+
next = at < 0 ? shown.length - 1 : (at - 1 + shown.length) % shown.length;
|
|
312
|
+
}
|
|
313
|
+
else if (event.key === 'Home') {
|
|
314
|
+
next = 0;
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
next = shown.length - 1;
|
|
318
|
+
}
|
|
319
|
+
const target = shown[next];
|
|
320
|
+
if (!target) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.activeValue = target.value;
|
|
324
|
+
/* Chosen on the way past only where moving is choosing. With `multiple` a
|
|
325
|
+
reader passing over four options on the way to the fifth did not mean to
|
|
326
|
+
pick all five.
|
|
327
|
+
|
|
328
|
+
Nor where the keys were forwarded. A list with the focus on it selects as
|
|
329
|
+
it moves, which is the pattern for a listbox; a combobox's arrows move
|
|
330
|
+
the highlight and Enter takes it, which is the pattern for a combobox,
|
|
331
|
+
and the two differ by exactly which element the reader is in. Measured
|
|
332
|
+
before this: the first Down in a combobox committed the second option
|
|
333
|
+
and shut the panel, one keystroke into the interaction. */
|
|
334
|
+
if (!this.multiple && !this.forwarded) {
|
|
335
|
+
this.choose(target.value);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* Narrows the list as the reader types, announcing it first.
|
|
340
|
+
*
|
|
341
|
+
* `helix-filter` and not `helix-change`, because nothing about the value
|
|
342
|
+
* changes: the options are hidden rather than removed, and telling a consumer
|
|
343
|
+
* their answer moved when it did not is worse than telling them nothing. It
|
|
344
|
+
* told them nothing before this.
|
|
345
|
+
*/
|
|
346
|
+
this.handleFilter = (event) => {
|
|
347
|
+
// Stopped for the reason `Input.handleInput` gives: the inner control's own
|
|
348
|
+
// `input` is composed and would reach a consumer beside this element's.
|
|
349
|
+
event.stopPropagation();
|
|
350
|
+
const control = event.target;
|
|
351
|
+
this.requestFilter(control.value);
|
|
352
|
+
if (control.value !== this.filter) {
|
|
353
|
+
control.value = this.filter;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Sends the keys that belong to the list to the list.
|
|
358
|
+
*
|
|
359
|
+
* A reader filtering is still choosing: they type two letters and press Down
|
|
360
|
+
* to reach what is left, and the field would otherwise swallow it because the
|
|
361
|
+
* arrows move a caret. Typing keys stay with the field, which is what makes
|
|
362
|
+
* this different from forwarding everything.
|
|
363
|
+
*
|
|
364
|
+
* Escape clears rather than closing. What frames the list may close on Escape
|
|
365
|
+
* too, so this stops the event when there is something to clear: one press
|
|
366
|
+
* empties the filter, a second reaches whatever is above and closes it. A
|
|
367
|
+
* filter that closed the panel would lose the reader's place for a keystroke
|
|
368
|
+
* they meant as an undo.
|
|
369
|
+
*/
|
|
370
|
+
this.handleFilterKeydown = (event) => {
|
|
371
|
+
if (event.key === 'Escape' && this.filter !== '') {
|
|
372
|
+
/* Stopped and prevented whether or not the clearing stands. The reader
|
|
373
|
+
pressed Escape at a filtered list, so the key was for this list either
|
|
374
|
+
way, and letting it through to close the surface around it would be a
|
|
375
|
+
second thing happening from one press. */
|
|
376
|
+
event.stopPropagation();
|
|
377
|
+
event.preventDefault();
|
|
378
|
+
this.requestFilter('');
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (!MOVES.has(event.key)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
/* **Forwarded, because the reader is in the field.** `navigate` says the
|
|
385
|
+
rule this has to follow: a listbox and a combobox differ by which element
|
|
386
|
+
the focus is in, not by a mode, and a reader typing into a filter is in
|
|
387
|
+
the second case whether or not the field belongs to this element. So the
|
|
388
|
+
arrows move the highlight and Enter takes it, which is what Enter already
|
|
389
|
+
does by bubbling to the host handler.
|
|
390
|
+
|
|
391
|
+
**What was here instead moved focus to `[part="items"]` and re-dispatched
|
|
392
|
+
the key.** Both halves of that were wrong, and measured on a bare
|
|
393
|
+
`<helix-listbox filterable>` one Down produced: two `helix-change` events,
|
|
394
|
+
`a` then `b`, and a highlight on the *second* option. Focus on the list
|
|
395
|
+
meant moving chose, so a single press committed a value the reader was
|
|
396
|
+
arrowing past; and the original press went on bubbling to the same host
|
|
397
|
+
handler the re-dispatch had already reached, so every key was handled
|
|
398
|
+
twice and the list advanced two rows for one press. In `Command` that
|
|
399
|
+
reads as a palette that navigates away the moment a reader presses Down.
|
|
400
|
+
|
|
401
|
+
`stopPropagation` is what closes the second half: the field handled the
|
|
402
|
+
key, so it must not arrive at the host as well. The move keys are the
|
|
403
|
+
only ones taken, so Enter, Tab and every character still pass. */
|
|
404
|
+
event.stopPropagation();
|
|
405
|
+
this.navigate(event);
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* What the list is narrowed to, if anything.
|
|
410
|
+
*
|
|
411
|
+
* Filtering hides rather than removes, so nothing about the value changes and
|
|
412
|
+
* the list is still the consumer's own elements in their own order. Reading
|
|
413
|
+
* gives what is being filtered on; writing declares it theirs, and
|
|
414
|
+
* `default-filter` seeds one the list owns.
|
|
415
|
+
*
|
|
416
|
+
*
|
|
417
|
+
*/
|
|
418
|
+
get filter() {
|
|
419
|
+
return this.filterState ?? this.defaultFilter;
|
|
420
|
+
}
|
|
421
|
+
set filter(next) {
|
|
422
|
+
const previous = this.filter;
|
|
423
|
+
this.filterState = next;
|
|
424
|
+
this.requestUpdate('filter', previous);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Which option is chosen, as an index, or `-1` where none is.
|
|
428
|
+
*
|
|
429
|
+
* `<select>`'s own pair, and this control is what stands in for one. The
|
|
430
|
+
* options are the consumer's own light-DOM children, so an index is a
|
|
431
|
+
* position among the elements they wrote rather than into anything this
|
|
432
|
+
* renders, which is what makes it the same number `<select>` would give.
|
|
433
|
+
*
|
|
434
|
+
* With `multiple` it is the first chosen one, which is also what the platform
|
|
435
|
+
* answers: a single index cannot describe a set, and `value` is the property
|
|
436
|
+
* that can.
|
|
437
|
+
*/
|
|
438
|
+
get selectedIndex() {
|
|
439
|
+
return this.options.findIndex((option) => option.selected);
|
|
440
|
+
}
|
|
441
|
+
set selectedIndex(at) {
|
|
442
|
+
const option = this.options[at];
|
|
443
|
+
this.value = option?.value ?? '';
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Which kind of chooser this is, in the platform's two words.
|
|
447
|
+
*
|
|
448
|
+
* Derived rather than declared, because `multiple` already decides it and a
|
|
449
|
+
* second property could disagree.
|
|
450
|
+
*/
|
|
451
|
+
get type() {
|
|
452
|
+
return this.multiple ? 'select-multiple' : 'select-one';
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Every option, in the order a reader meets them.
|
|
456
|
+
*
|
|
457
|
+
* Descendants rather than children, because a group puts a level between the
|
|
458
|
+
* list and its options and the keyboard has to move through a grouped list
|
|
459
|
+
* exactly as it moves through a flat one.
|
|
460
|
+
*/
|
|
461
|
+
get options() {
|
|
462
|
+
return this.collect(this.children);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Walks the tree, and walks *through* a slot when it meets one.
|
|
466
|
+
*
|
|
467
|
+
* Descendants rather than children, because a group puts a level between the
|
|
468
|
+
* list and its options and the keyboard has to move through a grouped list
|
|
469
|
+
* exactly as it moves through a flat one.
|
|
470
|
+
*
|
|
471
|
+
* Through a slot because that is how this gets composed. `Select` renders a
|
|
472
|
+
* listbox in its own shadow root with a slot inside it, so the options are
|
|
473
|
+
* the *select's* light DOM and the listbox's only child is the slot: a plain
|
|
474
|
+
* `querySelectorAll` finds the slot and no options at all, and the whole
|
|
475
|
+
* component comes up empty. Found the first time the two were put together,
|
|
476
|
+
* which is the argument for building the pieces separately rather than
|
|
477
|
+
* discovering it inside a finished Select.
|
|
478
|
+
*/
|
|
479
|
+
collect(nodes) {
|
|
480
|
+
const found = [];
|
|
481
|
+
for (const node of nodes) {
|
|
482
|
+
if (node instanceof HelixHeadlessOption) {
|
|
483
|
+
found.push(node);
|
|
484
|
+
}
|
|
485
|
+
else if (node instanceof HTMLSlotElement) {
|
|
486
|
+
found.push(...this.collect(node.assignedElements({ flatten: true })));
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
found.push(...this.collect(node.children));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return found;
|
|
493
|
+
}
|
|
494
|
+
/** The groups, found the same way and for the same reason. */
|
|
495
|
+
get groups() {
|
|
496
|
+
const found = [];
|
|
497
|
+
const walk = (nodes) => {
|
|
498
|
+
for (const node of nodes) {
|
|
499
|
+
if (node instanceof HelixHeadlessOptionGroup) {
|
|
500
|
+
found.push(node);
|
|
501
|
+
}
|
|
502
|
+
if (node instanceof HTMLSlotElement) {
|
|
503
|
+
walk(node.assignedElements({ flatten: true }));
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
walk(node.children);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
walk(this.children);
|
|
511
|
+
return found;
|
|
512
|
+
}
|
|
513
|
+
/** The options a filter has left showing. */
|
|
514
|
+
get shown() {
|
|
515
|
+
return this.options.filter((option) => !option.hidden);
|
|
516
|
+
}
|
|
517
|
+
/** The chosen values, which is a list whether or not `multiple` is set. */
|
|
518
|
+
get values() {
|
|
519
|
+
return this.value === '' ? [] : this.value.split(',');
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Applies a choice, announcing first so a listener can refuse it.
|
|
523
|
+
*
|
|
524
|
+
* With `multiple` a choice is a toggle, because the same press has to be able
|
|
525
|
+
* to take an answer back; without it a choice replaces whatever was there.
|
|
526
|
+
*/
|
|
527
|
+
choose(value) {
|
|
528
|
+
const chosen = this.values;
|
|
529
|
+
const next = this.multiple
|
|
530
|
+
? chosen.includes(value)
|
|
531
|
+
? chosen.filter((each) => each !== value)
|
|
532
|
+
: [...chosen, value]
|
|
533
|
+
: [value];
|
|
534
|
+
const joined = next.join(',');
|
|
535
|
+
if (joined === this.value) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
/* Through the mixin rather than hand-rolled. It did the same two steps in
|
|
539
|
+
the same order, and doing them here meant the control could not be
|
|
540
|
+
owned: writing `value` is how a consumer says it is theirs, so a listbox
|
|
541
|
+
writing its own would take ownership away from them on every change. */
|
|
542
|
+
this.requestValue(joined);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Runs the list's own key handling for a caller whose focus is elsewhere.
|
|
546
|
+
*
|
|
547
|
+
* A combobox keeps focus in its text field, because that is the whole of what
|
|
548
|
+
* a combobox is, and still has to move this list with the arrows. Without
|
|
549
|
+
* this it would have to reimplement the moving, the wrapping, the skipping of
|
|
550
|
+
* disabled options and the seeding from the current value, and the second
|
|
551
|
+
* copy would drift from this one the first time either changed.
|
|
552
|
+
*
|
|
553
|
+
* Public and taking the event rather than a direction, so a caller forwards
|
|
554
|
+
* what it received and this decides what the key means. `preventDefault` is
|
|
555
|
+
* called in here on the keys it acts on, so a caller can hand over every
|
|
556
|
+
* keydown and let the list ignore the ones that are not its business.
|
|
557
|
+
*
|
|
558
|
+
* The entry point is also what says whether moving chooses, which is why the
|
|
559
|
+
* flag is set here rather than exposed as a property. The difference between
|
|
560
|
+
* a listbox and a combobox is not a mode a consumer picks: it is which
|
|
561
|
+
* element the reader's focus is in, and that is exactly what calling this
|
|
562
|
+
* rather than being typed into means. Enter and Space still choose, because
|
|
563
|
+
* those are the caller asking for the answer rather than for a move.
|
|
564
|
+
*/
|
|
565
|
+
navigate(event) {
|
|
566
|
+
this.forwarded = true;
|
|
567
|
+
try {
|
|
568
|
+
this.handleKeydown(event);
|
|
569
|
+
}
|
|
570
|
+
finally {
|
|
571
|
+
this.forwarded = false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Brings the chosen option to rest in the middle of the list.
|
|
576
|
+
*
|
|
577
|
+
* A method rather than something this does on its own, because a listbox
|
|
578
|
+
* opening on its answer usually wants it merely *visible* and the browser
|
|
579
|
+
* already manages that: centering is what a caller wants when the list is being
|
|
580
|
+
* read as a dial rather than scanned as a list, which is what Time Picker's wheels
|
|
581
|
+
* does with three of these.
|
|
582
|
+
*
|
|
583
|
+
* A delta from the two boxes rather than `offsetTop`. An option is a slotted
|
|
584
|
+
* light DOM child, so its `offsetParent` is whatever is positioned above it,
|
|
585
|
+
* which is not this list and is not guaranteed to be anything: measuring both
|
|
586
|
+
* rects and moving by the difference needs no such assumption.
|
|
587
|
+
*/
|
|
588
|
+
reveal() {
|
|
589
|
+
const items = this.renderRoot?.querySelector('[part="items"]');
|
|
590
|
+
const chosen = this.options.find((option) => option.selected && !option.hidden);
|
|
591
|
+
if (!items || !chosen) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
/* Twice, and the second pass is not superstition. The first delta is
|
|
595
|
+
computed against whatever layout is current when this is called, and
|
|
596
|
+
inside a panel that has just been revealed it is a frame behind: measured,
|
|
597
|
+
one call landed a whole row short, 672 where 704 was wanted, and calling
|
|
598
|
+
it again from a settled layout put it exactly on center. Reading a rect
|
|
599
|
+
forces layout, so the second pass sees the result of the first and
|
|
600
|
+
corrects the remainder. It stops as soon as there is nothing left to
|
|
601
|
+
correct, so the usual case is one pass and a cheap check. */
|
|
602
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
603
|
+
const box = items.getBoundingClientRect();
|
|
604
|
+
const row = chosen.getBoundingClientRect();
|
|
605
|
+
if (box.height === 0 || row.height === 0) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const off = row.top + row.height / 2 - (box.top + box.height / 2);
|
|
609
|
+
if (Math.abs(off) < 1) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
items.scrollTop += off;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/** Hides what the filter excludes, and writes the rest onto the options. */
|
|
616
|
+
place() {
|
|
617
|
+
const needle = this.filter.trim().toLowerCase();
|
|
618
|
+
const chosen = new Set(this.values);
|
|
619
|
+
for (const option of this.options) {
|
|
620
|
+
option.hidden = needle !== '' && !option.text.toLowerCase().includes(needle);
|
|
621
|
+
option.selected = chosen.has(option.value);
|
|
622
|
+
}
|
|
623
|
+
/* A group whose options the filter has all hidden goes with them. CSS
|
|
624
|
+
cannot do this: a heading and its options are siblings under the group,
|
|
625
|
+
so the rule would be `:host(:has(...))`, which is dropped at parse time.
|
|
626
|
+
The listbox can, because it is the thing that did the hiding and already
|
|
627
|
+
knows which. Left showing, the picture was a heading over nothing, which
|
|
628
|
+
reads as a group whose contents failed to render. */
|
|
629
|
+
for (const group of this.groups) {
|
|
630
|
+
const inside = this.collect(group.children);
|
|
631
|
+
group.hidden = inside.length > 0 && inside.every((option) => option.hidden);
|
|
632
|
+
}
|
|
633
|
+
/* The keyboard cannot sit on something the filter has hidden, so it moves
|
|
634
|
+
to the first thing still showing rather than nowhere: a list whose active
|
|
635
|
+
option has just been filtered away is a list the next arrow press would
|
|
636
|
+
start from the top of anyway.
|
|
637
|
+
|
|
638
|
+
But only where there is a filter to have hidden it, or a highlight worth
|
|
639
|
+
rescuing. Clearing one must not *invent* a highlight out of nothing:
|
|
640
|
+
measured, a combobox clears its filter as it closes, and that alone
|
|
641
|
+
parked the highlight on the first option while the panel was shut, so
|
|
642
|
+
the reader's first Down arrived at the second. */
|
|
643
|
+
const shown = this.shown;
|
|
644
|
+
const rescue = needle !== '' || this.activeValue !== '';
|
|
645
|
+
if (rescue && !shown.some((option) => option.value === this.activeValue)) {
|
|
646
|
+
this.activeValue = shown.find((option) => !option.disabled)?.value ?? '';
|
|
647
|
+
}
|
|
648
|
+
for (const option of this.options) {
|
|
649
|
+
option.active = option.value === this.activeValue;
|
|
650
|
+
}
|
|
651
|
+
/* And the marks, from the same needle that did the hiding: the two answers
|
|
652
|
+
have to come from one place or a list can show a match it does not mark. */
|
|
653
|
+
if (needle === '' || !paintable()) {
|
|
654
|
+
matched.delete(this);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
matched.set(this, shown.flatMap((option) => rangeFor(option, needle)));
|
|
658
|
+
}
|
|
659
|
+
paintMatches();
|
|
660
|
+
const anyShown = shown.length > 0;
|
|
661
|
+
if (anyShown !== this.hasMatches) {
|
|
662
|
+
this.hasMatches = anyShown;
|
|
663
|
+
}
|
|
664
|
+
this.counted = true;
|
|
665
|
+
const active = shown.find((option) => option.value === this.activeValue);
|
|
666
|
+
if (active) {
|
|
667
|
+
const items = this.renderRoot.querySelector('[part="items"]');
|
|
668
|
+
scrollItemIntoView(active, { padding: 4, ...(items ? { scroller: items } : {}) });
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/** Reports the answer and the verdict on it. */
|
|
672
|
+
sync() {
|
|
673
|
+
this.internals.setFormValue(this.value === '' ? null : this.value);
|
|
674
|
+
this.reportRequired(this.value === '', 'Choose one to continue.', 'items');
|
|
675
|
+
}
|
|
676
|
+
connectedCallback() {
|
|
677
|
+
super.connectedCallback();
|
|
678
|
+
this.addEventListener('helix-select', this.handleSelection);
|
|
679
|
+
this.addEventListener('keydown', this.handleKeydown);
|
|
680
|
+
}
|
|
681
|
+
disconnectedCallback() {
|
|
682
|
+
super.disconnectedCallback();
|
|
683
|
+
this.removeEventListener('helix-select', this.handleSelection);
|
|
684
|
+
this.removeEventListener('keydown', this.handleKeydown);
|
|
685
|
+
/* Its marks go with it. The registry is the document's and outlives this
|
|
686
|
+
element, so a list removed while filtered would leave ranges over text
|
|
687
|
+
that is no longer on the page. */
|
|
688
|
+
matched.delete(this);
|
|
689
|
+
paintMatches();
|
|
690
|
+
}
|
|
691
|
+
willUpdate(changed) {
|
|
692
|
+
super.willUpdate(changed);
|
|
693
|
+
hostAria(this.internals, { label: this.label, role: 'listbox' });
|
|
694
|
+
this.internals.ariaMultiSelectable = this.multiple ? 'true' : null;
|
|
695
|
+
}
|
|
696
|
+
/*
|
|
697
|
+
* Points the focused list at the option the keyboard is on.
|
|
698
|
+
*
|
|
699
|
+
* **The gap this closes.** Focus stays on `[part="items"]` while a highlight
|
|
700
|
+
* moves between options, which is the half of the ARIA listbox pattern we
|
|
701
|
+
* had. The other half is saying *which* option, and nothing did: a screen
|
|
702
|
+
* reader heard the list and its selection and never heard the item being
|
|
703
|
+
* moved over. Neither `aria-activedescendant` nor a roving `tabindex` was
|
|
704
|
+
* wired, and the pattern requires one of the two.
|
|
705
|
+
*
|
|
706
|
+
* **An element reference and not an id, because an IDREF cannot do this.**
|
|
707
|
+
* The options are the consumer's light DOM and the element that takes focus
|
|
708
|
+
* is in this shadow root, so `aria-activedescendant="whatever"` resolves
|
|
709
|
+
* against the shadow tree, finds nothing, and is dropped in silence. Element
|
|
710
|
+
* references are the platform's answer to exactly that, and they resolve
|
|
711
|
+
* outward: a shadow-tree element may point at a light-DOM one, which is the
|
|
712
|
+
* direction this needs.
|
|
713
|
+
*
|
|
714
|
+
* Feature-tested rather than assumed, for the reason `hostAria` gives:
|
|
715
|
+
* assigning to a property an engine does not implement is a no-op rather than
|
|
716
|
+
* an error, so the guard is what makes the absence visible to anyone reading
|
|
717
|
+
* this. Where it is missing the list still works and still announces its
|
|
718
|
+
* selection; what is lost is the running commentary as the keys move.
|
|
719
|
+
*/
|
|
720
|
+
/**
|
|
721
|
+
* Where the reader is, and failing that where the answer is.
|
|
722
|
+
*
|
|
723
|
+
* `activeValue` starts empty, so the first arrow press in a list that already
|
|
724
|
+
* has a chosen option started from nowhere: Down went to the first option,
|
|
725
|
+
* which is a move backwards from anything below it, and Up went to the last.
|
|
726
|
+
* Seeding from the value means the first press moves from what the reader can
|
|
727
|
+
* see is selected.
|
|
728
|
+
*
|
|
729
|
+
* **The keyboard knew this and the announcement did not.** `linkActive` asked
|
|
730
|
+
* for `activeValue` alone, so a list that had been given a value and not yet
|
|
731
|
+
* arrowed through pointed `aria-activedescendant` at nothing: a screen reader
|
|
732
|
+
* was told the list and its selection, and nothing about where the cursor
|
|
733
|
+
* was, until a key moved it. Both read this now, so the thing announced and
|
|
734
|
+
* the thing the arrows move from cannot disagree.
|
|
735
|
+
*/
|
|
736
|
+
get currentValue() {
|
|
737
|
+
return this.activeValue || (this.value.split(',')[0] ?? '');
|
|
738
|
+
}
|
|
739
|
+
linkActive() {
|
|
740
|
+
/* Whichever element the reader is actually in. `aria-activedescendant` is
|
|
741
|
+
read from the focused element, so pointing the list at the active option
|
|
742
|
+
says nothing while focus is in the filter, which is where it now stays.
|
|
743
|
+
A filterable list announces from its field and a plain one from its
|
|
744
|
+
items, for the same reason the arrows are forwarded in one and not the
|
|
745
|
+
other. */
|
|
746
|
+
const list = this.renderRoot.querySelector(this.filterable ? '[part="search-field"]' : '[part="items"]');
|
|
747
|
+
if (!list || !('ariaActiveDescendantElement' in list)) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const active = this.options.find((option) => option.value === this.currentValue);
|
|
751
|
+
list.ariaActiveDescendantElement = active ?? null;
|
|
752
|
+
}
|
|
753
|
+
updated(changed) {
|
|
754
|
+
super.updated(changed);
|
|
755
|
+
this.place();
|
|
756
|
+
this.sync();
|
|
757
|
+
this.linkHost();
|
|
758
|
+
this.linkActive();
|
|
759
|
+
}
|
|
760
|
+
/** Requests a narrowing, and reports whether the request stood. */
|
|
761
|
+
requestFilter(next) {
|
|
762
|
+
if (next === this.filter) {
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
return requestChange({
|
|
766
|
+
apply: (value) => {
|
|
767
|
+
const previous = this.filter;
|
|
768
|
+
this.filterState = value;
|
|
769
|
+
this.requestUpdate('filter', previous);
|
|
770
|
+
},
|
|
771
|
+
event: (value) => new HelixFilterEvent({ value }),
|
|
772
|
+
host: this,
|
|
773
|
+
next,
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Focusing the host focuses the list inside it.
|
|
778
|
+
*
|
|
779
|
+
* The focusable node is `[part="items"]`, a div in this shadow root, and a
|
|
780
|
+
* caller outside has no way to reach it: `select` opened its panel and called
|
|
781
|
+
* `.focus()` on this element, which is not focusable, so focus stayed on the
|
|
782
|
+
* trigger and the arrow keys never reached the options at all. Delegation is
|
|
783
|
+
* the platform answering that, and it keeps `document.activeElement` on the
|
|
784
|
+
* host, which is what a caller reasons about.
|
|
785
|
+
*/
|
|
786
|
+
static { this.shadowRootOptions = {
|
|
787
|
+
...HelixFormElement.shadowRootOptions,
|
|
788
|
+
delegatesFocus: true,
|
|
789
|
+
}; }
|
|
790
|
+
/**
|
|
791
|
+
* The mark in the filter's field, behind a seam.
|
|
792
|
+
*
|
|
793
|
+
* Slot fallback, so a consumer replaces it by slotting; and a method, so the
|
|
794
|
+
* tier above replaces it by overriding. Those answer different people: the
|
|
795
|
+
* slot is for the page and the method is for whoever is building the styled
|
|
796
|
+
* layer, and a fallback with only the first is one a tier can restyle and
|
|
797
|
+
* cannot change.
|
|
798
|
+
*/
|
|
799
|
+
renderFilterIcon() {
|
|
800
|
+
return html `<slot name="filter-icon">
|
|
801
|
+
<svg part="filter-mark" fill="currentColor" viewBox=${magnifyingGlass.viewBox}>
|
|
802
|
+
${unsafeSVG(magnifyingGlass.content)}
|
|
803
|
+
</svg>
|
|
804
|
+
</slot>`;
|
|
805
|
+
}
|
|
806
|
+
render() {
|
|
807
|
+
/* One tab stop for the whole list, which is what a listbox is in the tab
|
|
808
|
+
order: the arrows move within it. `tabindex` on the wrapper rather than
|
|
809
|
+
the host so the host stays as the consumer wrote it. */
|
|
810
|
+
return html `${this.renderGroupLabel()}
|
|
811
|
+
${this.filterable
|
|
812
|
+
? html `<div part="search">
|
|
813
|
+
<span part="filter-icon" aria-hidden="true">
|
|
814
|
+
${this.renderFilterIcon()}
|
|
815
|
+
</span>
|
|
816
|
+
<input
|
|
817
|
+
part="search-field"
|
|
818
|
+
type="text"
|
|
819
|
+
autocomplete=${this.autocomplete || nothing}
|
|
820
|
+
spellcheck="false"
|
|
821
|
+
.value=${this.filter}
|
|
822
|
+
placeholder=${this.filterLabel}
|
|
823
|
+
aria-label=${this.filterLabel}
|
|
824
|
+
aria-controls="items"
|
|
825
|
+
?disabled=${this.isDisabled}
|
|
826
|
+
@input=${this.handleFilter}
|
|
827
|
+
@keydown=${this.handleFilterKeydown}
|
|
828
|
+
/>
|
|
829
|
+
</div>`
|
|
830
|
+
: ''}
|
|
831
|
+
<div
|
|
832
|
+
part="items"
|
|
833
|
+
id="items"
|
|
834
|
+
aria-busy=${this.loading ? 'true' : nothing}
|
|
835
|
+
${
|
|
836
|
+
/* `isDisabled`, matching the filter input above it. The two lines
|
|
837
|
+
disagreed, so a listbox in a disabled fieldset kept its tab stop
|
|
838
|
+
and took arrow keys while its own filter was correctly out. */
|
|
839
|
+
''}
|
|
840
|
+
tabindex=${this.isDisabled ? -1 : 0}
|
|
841
|
+
>
|
|
842
|
+
<slot @slotchange=${() => this.place()}></slot>
|
|
843
|
+
${
|
|
844
|
+
/* Loading and empty come from the shared mixin; what is local is what
|
|
845
|
+
counts as content. A list with a filter that matched nothing is
|
|
846
|
+
empty, and one with no filter and no options is empty too, but a
|
|
847
|
+
list still waiting on its options is neither. */
|
|
848
|
+
this.renderStatus(this.counted && !this.loading
|
|
849
|
+
? this.filter === ''
|
|
850
|
+
? this.options.length > 0
|
|
851
|
+
: this.hasMatches
|
|
852
|
+
: true)}
|
|
853
|
+
</div>
|
|
854
|
+
${this.renderHint()}${this.renderFeedback()}`;
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
__decorate([
|
|
858
|
+
property()
|
|
859
|
+
], HelixHeadlessListbox.prototype, "autocomplete", void 0);
|
|
860
|
+
__decorate([
|
|
861
|
+
property()
|
|
862
|
+
], HelixHeadlessListbox.prototype, "filter", null);
|
|
863
|
+
__decorate([
|
|
864
|
+
property({ attribute: 'default-filter' })
|
|
865
|
+
], HelixHeadlessListbox.prototype, "defaultFilter", void 0);
|
|
866
|
+
__decorate([
|
|
867
|
+
property({ attribute: 'filterable', reflect: true, type: Boolean })
|
|
868
|
+
], HelixHeadlessListbox.prototype, "filterable", void 0);
|
|
869
|
+
__decorate([
|
|
870
|
+
property({ attribute: 'filter-label' })
|
|
871
|
+
], HelixHeadlessListbox.prototype, "filterLabel", void 0);
|
|
872
|
+
__decorate([
|
|
873
|
+
property({ reflect: true, type: Boolean })
|
|
874
|
+
], HelixHeadlessListbox.prototype, "multiple", void 0);
|
|
875
|
+
__decorate([
|
|
876
|
+
property({ attribute: 'empty-text' })
|
|
877
|
+
], HelixHeadlessListbox.prototype, "emptyText", void 0);
|
|
878
|
+
__decorate([
|
|
879
|
+
state()
|
|
880
|
+
], HelixHeadlessListbox.prototype, "activeValue", void 0);
|
|
881
|
+
__decorate([
|
|
882
|
+
state()
|
|
883
|
+
], HelixHeadlessListbox.prototype, "hasMatches", void 0);
|
|
884
|
+
__decorate([
|
|
885
|
+
state()
|
|
886
|
+
], HelixHeadlessListbox.prototype, "counted", void 0);
|
|
887
|
+
HelixHeadlessListbox = __decorate([
|
|
888
|
+
customElement('helix-headless-listbox')
|
|
889
|
+
], HelixHeadlessListbox);
|
|
890
|
+
export { HelixHeadlessListbox };
|