@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,207 @@
|
|
|
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, HelixFormElement, MixinControllerChecked, MixinField, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { html, nothing } from 'lit';
|
|
9
|
+
import { property } from 'lit/decorators.js';
|
|
10
|
+
/**
|
|
11
|
+
* @summary A box that is ticked, or not, or neither.
|
|
12
|
+
*
|
|
13
|
+
* @description
|
|
14
|
+
* Renders a real `<input type="checkbox">`, so the space key, the focus ring,
|
|
15
|
+
* and the indeterminate glyph come from the platform. `delegatesFocus` puts
|
|
16
|
+
* focus on it, and it serializes into the server's markup, so the control is
|
|
17
|
+
* reachable before any script loads.
|
|
18
|
+
*
|
|
19
|
+
* **The input inside a shadow root submits nothing**, because the form is on
|
|
20
|
+
* the other side of the boundary. The host is form-associated instead and
|
|
21
|
+
* reports its own value through `ElementInternals`, which is also what carries
|
|
22
|
+
* validity and what answers a form reset.
|
|
23
|
+
*
|
|
24
|
+
* A checked box submits its `value`, and an unchecked one submits nothing at
|
|
25
|
+
* all. That is what a native checkbox does, and a form built around one reads
|
|
26
|
+
* the absence rather than a `false`.
|
|
27
|
+
*
|
|
28
|
+
* `indeterminate` is presentation and nothing else. It is neither checked nor
|
|
29
|
+
* unchecked to a reader, it never submits, and the first click resolves it: a
|
|
30
|
+
* third state that survives interaction is a control nobody can finish.
|
|
31
|
+
*
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
* @status experimental
|
|
34
|
+
* @category forms
|
|
35
|
+
* @aliases Tickbox, Toggle
|
|
36
|
+
* @documentation https://helix.fusion.dev/components/checkbox
|
|
37
|
+
*
|
|
38
|
+
* @slot - The label.
|
|
39
|
+
*
|
|
40
|
+
* @event {Event} change - Relayed from the input once the state has changed.
|
|
41
|
+
* @event {HelixBeforeToggleEvent} helix-before-toggle - Fired before the state changes, and cancelable.
|
|
42
|
+
* @event {Event} input - Announced once the state has moved.
|
|
43
|
+
*
|
|
44
|
+
* @csspart box - The frame the input is painted as, and the mark sits over.
|
|
45
|
+
* @csspart control - The input.
|
|
46
|
+
* @csspart feedback - What is wrong with it.
|
|
47
|
+
* @csspart field - The row the box and its label share, and the surface the card appearance draws.
|
|
48
|
+
* @csspart hint - Help under the control, while nothing is wrong.
|
|
49
|
+
* @csspart label - The label beside it.
|
|
50
|
+
* @csspart mark - The tick, or the dash when the box is indeterminate.
|
|
51
|
+
*/
|
|
52
|
+
let HelixHeadlessCheckbox = class HelixHeadlessCheckbox extends MixinControllerChecked(MixinField(HelixFormElement)) {
|
|
53
|
+
constructor() {
|
|
54
|
+
super(...arguments);
|
|
55
|
+
/* A consumer's `aria-describedby` names ids in their own tree and the control
|
|
56
|
+
is in this shadow root, so the reference has to be carried down as an
|
|
57
|
+
element rather than copied as an id. See `AriaForwardController`: measured
|
|
58
|
+
in Chrome's own tree, the host held the description and the control the
|
|
59
|
+
reader is actually on was exposed without it. */
|
|
60
|
+
this.aria = new AriaForwardController(this, {
|
|
61
|
+
target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
|
|
62
|
+
});
|
|
63
|
+
/**
|
|
64
|
+
* Value submitted when the box is checked.
|
|
65
|
+
*/
|
|
66
|
+
this.value = 'on';
|
|
67
|
+
/**
|
|
68
|
+
* Whether the box shows as neither checked nor unchecked.
|
|
69
|
+
*
|
|
70
|
+
* Presentation only. It never submits, and the first change resolves it.
|
|
71
|
+
*/
|
|
72
|
+
this.indeterminate = false;
|
|
73
|
+
this.handleChange = (event) => {
|
|
74
|
+
const input = event.target;
|
|
75
|
+
// The input has already flipped itself. Where the change is refused, or the
|
|
76
|
+
// element is controlled, it is put back to whatever the element resolves
|
|
77
|
+
// to, so the box never shows a state the element does not hold.
|
|
78
|
+
const next = input.checked;
|
|
79
|
+
/* The dash goes only where the change is taken.
|
|
80
|
+
*
|
|
81
|
+
* It was cleared first and unconditionally, so a consumer calling
|
|
82
|
+
* `preventDefault()` on the change kept the checked state and lost the
|
|
83
|
+
* third one: the box came back neither ticked nor dashed, which is a state
|
|
84
|
+
* nobody asked for and the element did not hold. Refusing a change should
|
|
85
|
+
* leave everything as it was, which is the argument the line below already
|
|
86
|
+
* makes for `checked`. */
|
|
87
|
+
if (this.requestChecked(next)) {
|
|
88
|
+
this.indeterminate = false;
|
|
89
|
+
}
|
|
90
|
+
input.checked = this.checked;
|
|
91
|
+
this.relayNativeEvent(event);
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Whether there is a label to leave room for, written to the host.
|
|
95
|
+
*
|
|
96
|
+
* The row is a flex line with a gap in it, so a label span rendered empty is
|
|
97
|
+
* still a box and still takes that gap: an unlabelled box came out with eight
|
|
98
|
+
* pixels of nothing after it. The input answers this by not rendering the
|
|
99
|
+
* element, which cannot work here, because a checkbox is named by its own
|
|
100
|
+
* content and the property is only that slot's fallback. Gating on the
|
|
101
|
+
* property would unlabel every `<helix-checkbox>Text</helix-checkbox>` ever
|
|
102
|
+
* written.
|
|
103
|
+
*
|
|
104
|
+
* So the slot is asked instead, the same way `Tooltip` asks its own: with
|
|
105
|
+
* `flatten`, because here the fallback counts, being where the property
|
|
106
|
+
* renders. Client only, so a server-rendered unlabelled checkbox carries the
|
|
107
|
+
* gap for one frame. That is a control with no accessible name, which has a
|
|
108
|
+
* worse problem than its spacing.
|
|
109
|
+
*/
|
|
110
|
+
this.handleLabelChange = () => {
|
|
111
|
+
this.syncLabelPresence();
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Focus is delegated to the input, so the host is never the focus target. */
|
|
115
|
+
static { this.shadowRootOptions = {
|
|
116
|
+
...HelixFormElement.shadowRootOptions,
|
|
117
|
+
delegatesFocus: true,
|
|
118
|
+
}; }
|
|
119
|
+
/** Reports the value and the validity the host stands behind. */
|
|
120
|
+
sync() {
|
|
121
|
+
this.internals.setFormValue(this.checked ? this.value : null);
|
|
122
|
+
this.reportRequired(!this.checked, 'Tick this box to continue.', 'control');
|
|
123
|
+
}
|
|
124
|
+
/** Returns the box to the state its markup declared. */
|
|
125
|
+
formResetCallback() {
|
|
126
|
+
this.indeterminate = false;
|
|
127
|
+
// `resetChecked` drops both halves now, the consumer's and the element's,
|
|
128
|
+
// so there is nothing to clear before it.
|
|
129
|
+
this.resetChecked();
|
|
130
|
+
}
|
|
131
|
+
willUpdate(changed) {
|
|
132
|
+
super.willUpdate(changed);
|
|
133
|
+
this.internals.ariaRequired = this.required ? 'true' : null;
|
|
134
|
+
}
|
|
135
|
+
updated(changed) {
|
|
136
|
+
super.updated(changed);
|
|
137
|
+
this.sync();
|
|
138
|
+
this.syncLabelPresence();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Asks the slot, and records the answer as a state.
|
|
142
|
+
*
|
|
143
|
+
* Called on `slotchange` and after every update, and the second is not
|
|
144
|
+
* belt-and-braces: **`slotchange` does not fire when nothing is assigned.**
|
|
145
|
+
* So a checkbox labeled only by its property, which is most of them, never
|
|
146
|
+
* heard about its own label and rendered without one. Caught by the spec that
|
|
147
|
+
* asks a labeled row to be wider than a bare one.
|
|
148
|
+
*
|
|
149
|
+
* **`:state(has-label)` and not `data-has-label`, since 27 Aug 2026.** This
|
|
150
|
+
* one could never have been in server output whatever it was called, the
|
|
151
|
+
* answer coming from `assignedNodes` and a server having no tree to ask, so
|
|
152
|
+
* it was an attribute that appeared on every labeled checkbox the instant it
|
|
153
|
+
* upgraded and on none that any server sent. See `HelixElement.toggleState`.
|
|
154
|
+
*/
|
|
155
|
+
syncLabelPresence() {
|
|
156
|
+
const slot = this.renderRoot.querySelector('[part="label"] slot');
|
|
157
|
+
const filled = (slot?.assignedNodes({ flatten: true }) ?? []).some((node) => node.textContent?.trim() !== '' || node.nodeType === Node.ELEMENT_NODE);
|
|
158
|
+
this.toggleState('has-label', filled);
|
|
159
|
+
/* And `hidden` on the region itself, because the state is on the host and
|
|
160
|
+
this tier has no sheet to turn one into the other: a checkbox with no
|
|
161
|
+
label kept an empty label box beside it. The styled rule stays, since it
|
|
162
|
+
hides through a descendant selector this cannot replace. */
|
|
163
|
+
this.renderRoot.querySelector('[part="label"]')?.toggleAttribute('hidden', !filled);
|
|
164
|
+
}
|
|
165
|
+
render() {
|
|
166
|
+
/* A real `<label>` around both, which is the whole of what makes the text a
|
|
167
|
+
target. It was a `<span>` beside the input, so pressing the words did
|
|
168
|
+
nothing: the styled tier's own description claimed the pair behaved like
|
|
169
|
+
a native label wrapping a checkbox, and nothing was doing that work.
|
|
170
|
+
|
|
171
|
+
Labeling is behavior, which is what earns an element here rather than a
|
|
172
|
+
bare slot: the wrapper is not decoration and there is no other way to get
|
|
173
|
+
it. The browser handles the forwarding, so there is no click handler to
|
|
174
|
+
write and none to get wrong.
|
|
175
|
+
|
|
176
|
+
The one line of description goes under that row, which is where a field
|
|
177
|
+
puts it too. */
|
|
178
|
+
return html `<label part="field">
|
|
179
|
+
<span part="box">
|
|
180
|
+
<input
|
|
181
|
+
part="control"
|
|
182
|
+
type="checkbox"
|
|
183
|
+
.checked=${this.checked}
|
|
184
|
+
.indeterminate=${this.indeterminate}
|
|
185
|
+
?disabled=${this.isDisabled}
|
|
186
|
+
?required=${this.required}
|
|
187
|
+
aria-describedby=${this.describedBy ?? nothing}
|
|
188
|
+
@change=${this.handleChange}
|
|
189
|
+
@input=${this.stopInner}
|
|
190
|
+
/>
|
|
191
|
+
<span part="mark" aria-hidden="true"></span>
|
|
192
|
+
</span>
|
|
193
|
+
<span part="label"><slot @slotchange=${this.handleLabelChange}>${this.label}</slot></span>
|
|
194
|
+
</label>
|
|
195
|
+
${this.renderHint()}${this.renderFeedback()}`;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
__decorate([
|
|
199
|
+
property()
|
|
200
|
+
], HelixHeadlessCheckbox.prototype, "value", void 0);
|
|
201
|
+
__decorate([
|
|
202
|
+
property({ reflect: true, type: Boolean })
|
|
203
|
+
], HelixHeadlessCheckbox.prototype, "indeterminate", void 0);
|
|
204
|
+
HelixHeadlessCheckbox = __decorate([
|
|
205
|
+
customElement('helix-headless-checkbox')
|
|
206
|
+
], HelixHeadlessCheckbox);
|
|
207
|
+
export { HelixHeadlessCheckbox };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './root/checkbox-group-root.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './root/checkbox-group-root.js';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { DisabledCascadeController, HelixFormElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
import { HelixHeadlessCheckbox } from '../../checkbox/root/checkbox-root.js';
|
|
4
|
+
declare const HelixHeadlessCheckboxGroup_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
|
|
5
|
+
/**
|
|
6
|
+
* @summary Groups related checkboxes, and reports them as one set.
|
|
7
|
+
*
|
|
8
|
+
* @description
|
|
9
|
+
* The host is the group, so `role="group"` sits on it through internals and
|
|
10
|
+
* `label` names it. A run of checkboxes with no name is announced as separate
|
|
11
|
+
* controls, which is what the grouping exists to say it is not.
|
|
12
|
+
*
|
|
13
|
+
* **Each checkbox still submits itself.** They are form-associated already and
|
|
14
|
+
* a native group is several inputs sharing a name, so this adds no form value
|
|
15
|
+
* of its own: it would submit the same answers twice. What it adds is the
|
|
16
|
+
* grouping, a shared `name` so it is written once, and one place to read and
|
|
17
|
+
* write the whole set.
|
|
18
|
+
*
|
|
19
|
+
* `value` is the checked values, comma separated, in the order the boxes are
|
|
20
|
+
* written. Setting it ticks exactly those and unticks the rest.
|
|
21
|
+
*
|
|
22
|
+
* **Its message is always given, never derived.** Every form-related member of
|
|
23
|
+
* `ElementInternals` throws on an element that is not form-associated, and this
|
|
24
|
+
* one is not, on purpose: the boxes submit themselves. So `feedback` is what
|
|
25
|
+
* shows here, and the browser has nothing to say about a set it does not know
|
|
26
|
+
* is a set.
|
|
27
|
+
*
|
|
28
|
+
* @since 0.1.0
|
|
29
|
+
* @status stable
|
|
30
|
+
* @category forms
|
|
31
|
+
* @aliases Checkbox list, Multi-select
|
|
32
|
+
* @documentation https://helix.fusion.dev/components/checkbox-group
|
|
33
|
+
*
|
|
34
|
+
* @slot - The checkboxes.
|
|
35
|
+
* @slot hint - Richer than a string.
|
|
36
|
+
* @slot label - Richer than a string, where the label is more than one.
|
|
37
|
+
*
|
|
38
|
+
* @event {Event} change - Announced once the value has moved.
|
|
39
|
+
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before any box changes, with the whole set. Cancelable: refusing it takes the decision and nothing moves.
|
|
40
|
+
* @event {Event} input - Announced once the value has moved.
|
|
41
|
+
*
|
|
42
|
+
* @csspart feedback - What is wrong with it.
|
|
43
|
+
* @csspart hint - Help under the set.
|
|
44
|
+
* @csspart items - The list itself, which is what `orientation` lays out.
|
|
45
|
+
* @csspart label - The group's name, over the set.
|
|
46
|
+
*/
|
|
47
|
+
export declare class HelixHeadlessCheckboxGroup extends HelixHeadlessCheckboxGroup_base {
|
|
48
|
+
/**
|
|
49
|
+
* The checked values, comma separated.
|
|
50
|
+
*/
|
|
51
|
+
value: string;
|
|
52
|
+
protected readonly cascade: DisabledCascadeController;
|
|
53
|
+
/** The boxes, in light-DOM order. Client only: the server has no tree. */
|
|
54
|
+
get boxes(): HelixHeadlessCheckbox[];
|
|
55
|
+
/**
|
|
56
|
+
* Pushes the group's name and value onto the boxes.
|
|
57
|
+
*
|
|
58
|
+
* As properties rather than attributes. A `name` written onto a box is a
|
|
59
|
+
* change to markup the consumer rendered, which a React consumer's hydration
|
|
60
|
+
* reports and does not patch up. A box that names itself keeps its own.
|
|
61
|
+
*/
|
|
62
|
+
private sync;
|
|
63
|
+
/**
|
|
64
|
+
* Reads the boxes' own state into `value`, once, before anything is pushed.
|
|
65
|
+
*
|
|
66
|
+
* `sync` used to refuse to write anything while `value` was empty, and the
|
|
67
|
+
* guard was there for this: a group written with no `value` and boxes that
|
|
68
|
+
* declare their own `checked` would have had them all cleared on the first
|
|
69
|
+
* update. What it also did was make `group.value = ''` do nothing at all, so
|
|
70
|
+
* the obvious way to clear a group silently did not, and the last box could
|
|
71
|
+
* not be unticked through the group either.
|
|
72
|
+
*
|
|
73
|
+
* Seeding is the honest version of the same intent. The markup decides the
|
|
74
|
+
* starting set, exactly as before, and from then on `value` is what the boxes
|
|
75
|
+
* follow, including when it is empty. The group's `value` is a derived read
|
|
76
|
+
* of the boxes rather than state it owns, so reading it out of them at the
|
|
77
|
+
* start is what it already means.
|
|
78
|
+
*/
|
|
79
|
+
private seed;
|
|
80
|
+
/**
|
|
81
|
+
* Reads a box's requested change into `value`, and says so once.
|
|
82
|
+
*
|
|
83
|
+
* The box is found from the event's target, which does two things. It tells
|
|
84
|
+
* the group which box asked, where the detail carries only the shared name.
|
|
85
|
+
* And it ignores the group's own dispatch, which lands on this same listener
|
|
86
|
+
* because the group is what it is dispatched from: without the guard the
|
|
87
|
+
* group answers itself until the stack gives out.
|
|
88
|
+
*
|
|
89
|
+
* The requested state comes from the event rather than from the box. Driving
|
|
90
|
+
* the boxes from `value` makes them controlled, so a controlled box applies
|
|
91
|
+
* nothing on its own and reports what it was asked for. Reading `checked`
|
|
92
|
+
* here would read the state it still holds and nothing would ever change.
|
|
93
|
+
*/
|
|
94
|
+
private readonly handleToggle;
|
|
95
|
+
/**
|
|
96
|
+
* Stops a box's own announcement, for the reason the request above is stopped.
|
|
97
|
+
*
|
|
98
|
+
* **The split gave the requests two names and left the notifications sharing
|
|
99
|
+
* one.** A box announces `input` and `change` from its own host now, which is
|
|
100
|
+
* what makes `onChange` work on a checkbox, and both bubble up through this
|
|
101
|
+
* group: measured, one click produced `change` from the group and `input` and
|
|
102
|
+
* `change` from the box, all three at a consumer listening here, two of them
|
|
103
|
+
* describing one box rather than the set. That is exactly the collision the
|
|
104
|
+
* comment on `handleToggle` describes, arriving by the other route.
|
|
105
|
+
*
|
|
106
|
+
* `stopImmediatePropagation` and not `stopPropagation`, and for the same
|
|
107
|
+
* reason it gives: the box's event and this group's own carry one name from
|
|
108
|
+
* what a consumer sees as one element, so stopping propagation alone still
|
|
109
|
+
* leaves every other listener on this host receiving both.
|
|
110
|
+
*
|
|
111
|
+
* A listener on the box itself is unaffected, which is where per-box handling
|
|
112
|
+
* belongs. Each box is form-associated and submits itself; what this stops is
|
|
113
|
+
* one box answering a question asked of the set.
|
|
114
|
+
*/
|
|
115
|
+
private readonly handleMemberNative;
|
|
116
|
+
connectedCallback(): void;
|
|
117
|
+
disconnectedCallback(): void;
|
|
118
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
119
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
120
|
+
/**
|
|
121
|
+
* Refuses an empty set where one was required, which nothing did.
|
|
122
|
+
*
|
|
123
|
+
* `required` arrives with `MixinField`, reflects, and paints the asterisk
|
|
124
|
+
* through the composed field sheet, and enforced nothing at all: no validity,
|
|
125
|
+
* no message, and nothing cascading to the boxes. A group marked required
|
|
126
|
+
* submitted empty.
|
|
127
|
+
*
|
|
128
|
+
* **The group is form-associated, and submits nothing.** It was not, on the
|
|
129
|
+
* reasoning that its boxes are the controls and each submits itself, which is
|
|
130
|
+
* still true: nothing here calls `setFormValue`, so the set that reaches the
|
|
131
|
+
* server is exactly the boxes it always was. What form association buys is the
|
|
132
|
+
* other half of participation, validity, and there is no other mechanism for
|
|
133
|
+
* it: `setValidity` throws `NotSupportedError` on an element the platform does
|
|
134
|
+
* not know is a control.
|
|
135
|
+
*
|
|
136
|
+
* So the fix that was written for "a group marked required submitted empty"
|
|
137
|
+
* threw on every update in a real browser, and happy-dom's shim does not
|
|
138
|
+
* throw, so the specs passed and the behavior never worked. Eleven of them
|
|
139
|
+
* failed the moment they were run in Chromium. It is also what makes the rest
|
|
140
|
+
* work: the form now refuses to submit, which fires `invalid`, which is what
|
|
141
|
+
* marks the field touched and puts the message on screen.
|
|
142
|
+
*
|
|
143
|
+
* Anchored on the list, because the browser needs somewhere to point the
|
|
144
|
+
* bubble.
|
|
145
|
+
*/
|
|
146
|
+
private validate;
|
|
147
|
+
protected updated(changed: PropertyValues): void;
|
|
148
|
+
protected render(): TemplateResult;
|
|
149
|
+
}
|
|
150
|
+
declare global {
|
|
151
|
+
interface HTMLElementTagNameMap {
|
|
152
|
+
'helix-headless-checkbox-group': HelixHeadlessCheckboxGroup;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export {};
|
|
@@ -0,0 +1,293 @@
|
|
|
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 { customElement, DisabledCascadeController, HelixBeforeChangeEvent, HelixFormElement, hostAria, MixinField, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { html } from 'lit';
|
|
9
|
+
import { property } from 'lit/decorators.js';
|
|
10
|
+
import { HelixHeadlessCheckbox } from '../../checkbox/root/checkbox-root.js';
|
|
11
|
+
/**
|
|
12
|
+
* @summary Groups related checkboxes, and reports them as one set.
|
|
13
|
+
*
|
|
14
|
+
* @description
|
|
15
|
+
* The host is the group, so `role="group"` sits on it through internals and
|
|
16
|
+
* `label` names it. A run of checkboxes with no name is announced as separate
|
|
17
|
+
* controls, which is what the grouping exists to say it is not.
|
|
18
|
+
*
|
|
19
|
+
* **Each checkbox still submits itself.** They are form-associated already and
|
|
20
|
+
* a native group is several inputs sharing a name, so this adds no form value
|
|
21
|
+
* of its own: it would submit the same answers twice. What it adds is the
|
|
22
|
+
* grouping, a shared `name` so it is written once, and one place to read and
|
|
23
|
+
* write the whole set.
|
|
24
|
+
*
|
|
25
|
+
* `value` is the checked values, comma separated, in the order the boxes are
|
|
26
|
+
* written. Setting it ticks exactly those and unticks the rest.
|
|
27
|
+
*
|
|
28
|
+
* **Its message is always given, never derived.** Every form-related member of
|
|
29
|
+
* `ElementInternals` throws on an element that is not form-associated, and this
|
|
30
|
+
* one is not, on purpose: the boxes submit themselves. So `feedback` is what
|
|
31
|
+
* shows here, and the browser has nothing to say about a set it does not know
|
|
32
|
+
* is a set.
|
|
33
|
+
*
|
|
34
|
+
* @since 0.1.0
|
|
35
|
+
* @status stable
|
|
36
|
+
* @category forms
|
|
37
|
+
* @aliases Checkbox list, Multi-select
|
|
38
|
+
* @documentation https://helix.fusion.dev/components/checkbox-group
|
|
39
|
+
*
|
|
40
|
+
* @slot - The checkboxes.
|
|
41
|
+
* @slot hint - Richer than a string.
|
|
42
|
+
* @slot label - Richer than a string, where the label is more than one.
|
|
43
|
+
*
|
|
44
|
+
* @event {Event} change - Announced once the value has moved.
|
|
45
|
+
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before any box changes, with the whole set. Cancelable: refusing it takes the decision and nothing moves.
|
|
46
|
+
* @event {Event} input - Announced once the value has moved.
|
|
47
|
+
*
|
|
48
|
+
* @csspart feedback - What is wrong with it.
|
|
49
|
+
* @csspart hint - Help under the set.
|
|
50
|
+
* @csspart items - The list itself, which is what `orientation` lays out.
|
|
51
|
+
* @csspart label - The group's name, over the set.
|
|
52
|
+
*/
|
|
53
|
+
let HelixHeadlessCheckboxGroup = class HelixHeadlessCheckboxGroup extends MixinField(HelixFormElement) {
|
|
54
|
+
constructor() {
|
|
55
|
+
/* `name` is inherited. It arrives with `HelixFormElement` reflected and with
|
|
56
|
+
the converter that makes an empty one absent rather than `name=""`, which
|
|
57
|
+
the copy declared here had neither of. What it means differs slightly and
|
|
58
|
+
the property does not: the group submits nothing under it, and hands it to
|
|
59
|
+
every box that has none. */
|
|
60
|
+
super(...arguments);
|
|
61
|
+
/**
|
|
62
|
+
* The checked values, comma separated.
|
|
63
|
+
*/
|
|
64
|
+
this.value = '';
|
|
65
|
+
/* Stated once, and the group never thinks about it again. See the
|
|
66
|
+
controller: the cascade was four hand calls from four different
|
|
67
|
+
methods, so which paths reached it differed by component and a fifth
|
|
68
|
+
group would have got none at all. */
|
|
69
|
+
this.cascade = new DisabledCascadeController(this, {
|
|
70
|
+
items: () => this.boxes,
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Reads a box's requested change into `value`, and says so once.
|
|
74
|
+
*
|
|
75
|
+
* The box is found from the event's target, which does two things. It tells
|
|
76
|
+
* the group which box asked, where the detail carries only the shared name.
|
|
77
|
+
* And it ignores the group's own dispatch, which lands on this same listener
|
|
78
|
+
* because the group is what it is dispatched from: without the guard the
|
|
79
|
+
* group answers itself until the stack gives out.
|
|
80
|
+
*
|
|
81
|
+
* The requested state comes from the event rather than from the box. Driving
|
|
82
|
+
* the boxes from `value` makes them controlled, so a controlled box applies
|
|
83
|
+
* nothing on its own and reports what it was asked for. Reading `checked`
|
|
84
|
+
* here would read the state it still holds and nothing would ever change.
|
|
85
|
+
*/
|
|
86
|
+
this.handleToggle = (event) => {
|
|
87
|
+
const target = event.target;
|
|
88
|
+
const changed = this.boxes.find((box) => box === target);
|
|
89
|
+
if (!changed) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
/* Still stopped, though the names no longer collide. A box's `helix-toggle`
|
|
93
|
+
bubbles, and the group is the single voice for the set: a consumer
|
|
94
|
+
listening here wants the whole answer, not one box's half of it. Before
|
|
95
|
+
the split this had to be `stopImmediatePropagation` rather than
|
|
96
|
+
`stopPropagation`, because the box's event and the group's own carried
|
|
97
|
+
the same name from the same host and the other listeners on it would have
|
|
98
|
+
received both. */
|
|
99
|
+
event.stopImmediatePropagation();
|
|
100
|
+
const wanted = event.detail.checked;
|
|
101
|
+
/* Announced before it is written, which is the way round it was not: the
|
|
102
|
+
group set its value and then dispatched, so `preventDefault()` refused a
|
|
103
|
+
set that had already changed.
|
|
104
|
+
|
|
105
|
+
Hand-rolled rather than through `MixinControllerValue`, and that is the
|
|
106
|
+
decision rather than an omission. The group is deliberately not
|
|
107
|
+
form-associated: its boxes are the form controls and each submits itself,
|
|
108
|
+
so the mixin, which is constrained to `HelixFormElement`, does not apply
|
|
109
|
+
to it. Its `value` is a derived read of the boxes rather than state it
|
|
110
|
+
owns, which is also why it is not controllable and why that is written
|
|
111
|
+
down rather than fixed.
|
|
112
|
+
|
|
113
|
+
The boxes have already moved either way. Each answered the reader before
|
|
114
|
+
this ran, so refusing here stops the group's value from following, and
|
|
115
|
+
the honest limit of that is recorded with the decision. */
|
|
116
|
+
const next = this.boxes
|
|
117
|
+
.filter((box) => (box === changed ? wanted : box.checked))
|
|
118
|
+
.map((box) => box.value)
|
|
119
|
+
.join(',');
|
|
120
|
+
if (!this.dispatchEvent(new HelixBeforeChangeEvent({ value: next }))) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.value = next;
|
|
124
|
+
/* Under the platform's own name, after the value has moved, so a consumer
|
|
125
|
+
binding `onChange` reads the group's new value off the target.
|
|
126
|
+
|
|
127
|
+
`input` beside it, because this is a discrete control and that is the
|
|
128
|
+
pair a native `<select>` sends and the pair every other discrete control
|
|
129
|
+
here sends through `MixinControllerValue`. The group had only `change`,
|
|
130
|
+
so a consumer binding `onInput` on the set heard the boxes and never the
|
|
131
|
+
group. */
|
|
132
|
+
this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
133
|
+
this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Stops a box's own announcement, for the reason the request above is stopped.
|
|
137
|
+
*
|
|
138
|
+
* **The split gave the requests two names and left the notifications sharing
|
|
139
|
+
* one.** A box announces `input` and `change` from its own host now, which is
|
|
140
|
+
* what makes `onChange` work on a checkbox, and both bubble up through this
|
|
141
|
+
* group: measured, one click produced `change` from the group and `input` and
|
|
142
|
+
* `change` from the box, all three at a consumer listening here, two of them
|
|
143
|
+
* describing one box rather than the set. That is exactly the collision the
|
|
144
|
+
* comment on `handleToggle` describes, arriving by the other route.
|
|
145
|
+
*
|
|
146
|
+
* `stopImmediatePropagation` and not `stopPropagation`, and for the same
|
|
147
|
+
* reason it gives: the box's event and this group's own carry one name from
|
|
148
|
+
* what a consumer sees as one element, so stopping propagation alone still
|
|
149
|
+
* leaves every other listener on this host receiving both.
|
|
150
|
+
*
|
|
151
|
+
* A listener on the box itself is unaffected, which is where per-box handling
|
|
152
|
+
* belongs. Each box is form-associated and submits itself; what this stops is
|
|
153
|
+
* one box answering a question asked of the set.
|
|
154
|
+
*/
|
|
155
|
+
this.handleMemberNative = (event) => {
|
|
156
|
+
if (event.target !== this) {
|
|
157
|
+
event.stopImmediatePropagation();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** The boxes, in light-DOM order. Client only: the server has no tree. */
|
|
162
|
+
get boxes() {
|
|
163
|
+
return [...this.children].filter((child) => child instanceof HelixHeadlessCheckbox);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Pushes the group's name and value onto the boxes.
|
|
167
|
+
*
|
|
168
|
+
* As properties rather than attributes. A `name` written onto a box is a
|
|
169
|
+
* change to markup the consumer rendered, which a React consumer's hydration
|
|
170
|
+
* reports and does not patch up. A box that names itself keeps its own.
|
|
171
|
+
*/
|
|
172
|
+
sync() {
|
|
173
|
+
const wanted = new Set(this.value
|
|
174
|
+
.split(',')
|
|
175
|
+
.map((entry) => entry.trim())
|
|
176
|
+
.filter(Boolean));
|
|
177
|
+
for (const box of this.boxes) {
|
|
178
|
+
if (this.name !== '' && box.name === '') {
|
|
179
|
+
box.name = this.name;
|
|
180
|
+
}
|
|
181
|
+
box.checked = wanted.has(box.value);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Reads the boxes' own state into `value`, once, before anything is pushed.
|
|
186
|
+
*
|
|
187
|
+
* `sync` used to refuse to write anything while `value` was empty, and the
|
|
188
|
+
* guard was there for this: a group written with no `value` and boxes that
|
|
189
|
+
* declare their own `checked` would have had them all cleared on the first
|
|
190
|
+
* update. What it also did was make `group.value = ''` do nothing at all, so
|
|
191
|
+
* the obvious way to clear a group silently did not, and the last box could
|
|
192
|
+
* not be unticked through the group either.
|
|
193
|
+
*
|
|
194
|
+
* Seeding is the honest version of the same intent. The markup decides the
|
|
195
|
+
* starting set, exactly as before, and from then on `value` is what the boxes
|
|
196
|
+
* follow, including when it is empty. The group's `value` is a derived read
|
|
197
|
+
* of the boxes rather than state it owns, so reading it out of them at the
|
|
198
|
+
* start is what it already means.
|
|
199
|
+
*/
|
|
200
|
+
seed() {
|
|
201
|
+
const checked = this.boxes.filter((box) => box.checked).map((box) => box.value);
|
|
202
|
+
if (checked.length > 0) {
|
|
203
|
+
this.value = checked.join(',');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
connectedCallback() {
|
|
207
|
+
super.connectedCallback();
|
|
208
|
+
this.addEventListener('helix-before-toggle', this.handleToggle);
|
|
209
|
+
this.addEventListener('change', this.handleMemberNative);
|
|
210
|
+
this.addEventListener('input', this.handleMemberNative);
|
|
211
|
+
}
|
|
212
|
+
disconnectedCallback() {
|
|
213
|
+
super.disconnectedCallback();
|
|
214
|
+
this.removeEventListener('helix-before-toggle', this.handleToggle);
|
|
215
|
+
this.removeEventListener('change', this.handleMemberNative);
|
|
216
|
+
this.removeEventListener('input', this.handleMemberNative);
|
|
217
|
+
}
|
|
218
|
+
willUpdate(changed) {
|
|
219
|
+
super.willUpdate(changed);
|
|
220
|
+
/* The string half, which is what a server can write. `linkHost` adds the
|
|
221
|
+
element references on the client, and those win where a consumer slotted
|
|
222
|
+
a label richer than a string. */
|
|
223
|
+
hostAria(this.internals, { label: this.label, role: 'group' });
|
|
224
|
+
this.internals.ariaRequired = this.required ? 'true' : null;
|
|
225
|
+
}
|
|
226
|
+
firstUpdated(changed) {
|
|
227
|
+
super.firstUpdated(changed);
|
|
228
|
+
/* Before the first `sync`, and only ever once: after this the group's value
|
|
229
|
+
is what the boxes follow. Client only, the server having no boxes to
|
|
230
|
+
read. */
|
|
231
|
+
if (this.value === '') {
|
|
232
|
+
this.seed();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Refuses an empty set where one was required, which nothing did.
|
|
237
|
+
*
|
|
238
|
+
* `required` arrives with `MixinField`, reflects, and paints the asterisk
|
|
239
|
+
* through the composed field sheet, and enforced nothing at all: no validity,
|
|
240
|
+
* no message, and nothing cascading to the boxes. A group marked required
|
|
241
|
+
* submitted empty.
|
|
242
|
+
*
|
|
243
|
+
* **The group is form-associated, and submits nothing.** It was not, on the
|
|
244
|
+
* reasoning that its boxes are the controls and each submits itself, which is
|
|
245
|
+
* still true: nothing here calls `setFormValue`, so the set that reaches the
|
|
246
|
+
* server is exactly the boxes it always was. What form association buys is the
|
|
247
|
+
* other half of participation, validity, and there is no other mechanism for
|
|
248
|
+
* it: `setValidity` throws `NotSupportedError` on an element the platform does
|
|
249
|
+
* not know is a control.
|
|
250
|
+
*
|
|
251
|
+
* So the fix that was written for "a group marked required submitted empty"
|
|
252
|
+
* threw on every update in a real browser, and happy-dom's shim does not
|
|
253
|
+
* throw, so the specs passed and the behavior never worked. Eleven of them
|
|
254
|
+
* failed the moment they were run in Chromium. It is also what makes the rest
|
|
255
|
+
* work: the form now refuses to submit, which fires `invalid`, which is what
|
|
256
|
+
* marks the field touched and puts the message on screen.
|
|
257
|
+
*
|
|
258
|
+
* Anchored on the list, because the browser needs somewhere to point the
|
|
259
|
+
* bubble.
|
|
260
|
+
*/
|
|
261
|
+
validate() {
|
|
262
|
+
this.reportRequired(this.value === '', 'Choose at least one to continue.', 'items');
|
|
263
|
+
}
|
|
264
|
+
updated(changed) {
|
|
265
|
+
super.updated(changed);
|
|
266
|
+
this.validate();
|
|
267
|
+
this.sync();
|
|
268
|
+
this.linkHost();
|
|
269
|
+
}
|
|
270
|
+
render() {
|
|
271
|
+
/* The list is an element of its own, because the label and the hint are the
|
|
272
|
+
host's children too and `orientation` lays out whatever is in the flex
|
|
273
|
+
container. On the host, a horizontal group would have put its own label
|
|
274
|
+
in the row beside the first box. */
|
|
275
|
+
return html `${this.renderGroupLabel()}
|
|
276
|
+
<div part="items">
|
|
277
|
+
${
|
|
278
|
+
/* A box arriving after first render got neither the shared name nor
|
|
279
|
+
the value's checked state, because nothing was listening: both
|
|
280
|
+
sibling groups wire this and this one did not. */
|
|
281
|
+
''}
|
|
282
|
+
<slot @slotchange=${() => this.requestUpdate()}></slot>
|
|
283
|
+
</div>
|
|
284
|
+
${this.renderHint()}${this.renderFeedback()}`;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
__decorate([
|
|
288
|
+
property()
|
|
289
|
+
], HelixHeadlessCheckboxGroup.prototype, "value", void 0);
|
|
290
|
+
HelixHeadlessCheckboxGroup = __decorate([
|
|
291
|
+
customElement('helix-headless-checkbox-group')
|
|
292
|
+
], HelixHeadlessCheckboxGroup);
|
|
293
|
+
export { HelixHeadlessCheckboxGroup };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './root/code-root.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './root/code-root.js';
|