@adia-ai/web-components 0.6.33 → 0.6.35
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/CHANGELOG.md +64 -0
- package/color/index.js +1 -1
- package/components/accordion/accordion-item.yaml +2 -2
- package/components/accordion/accordion.css +2 -2
- package/components/accordion/accordion.js +1 -1
- package/components/action-list/action-item.yaml +2 -2
- package/components/action-list/action-list.css +2 -2
- package/components/action-list/action-list.js +1 -1
- package/components/agent-artifact/{class.js → agent-artifact.class.js} +1 -1
- package/components/agent-artifact/agent-artifact.css +31 -31
- package/components/agent-artifact/agent-artifact.js +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
- package/components/agent-feedback-bar/agent-feedback-bar.js +1 -1
- package/components/agent-questions/agent-questions.css +57 -57
- package/components/agent-questions/agent-questions.js +1 -1
- package/components/agent-reasoning/agent-reasoning.css +62 -62
- package/components/agent-reasoning/agent-reasoning.js +1 -1
- package/components/agent-suggestions/agent-suggestions.css +4 -4
- package/components/agent-suggestions/agent-suggestions.js +1 -1
- package/components/agent-trace/agent-trace.css +53 -53
- package/components/alert/alert.a2ui.json +64 -1
- package/components/alert/{class.js → alert.class.js} +189 -2
- package/components/alert/alert.css +119 -41
- package/components/alert/alert.d.ts +14 -0
- package/components/alert/alert.js +1 -1
- package/components/alert/alert.test.js +184 -0
- package/components/alert/alert.yaml +114 -1
- package/components/avatar/avatar-group.yaml +2 -2
- package/components/avatar/avatar.css +27 -27
- package/components/avatar/avatar.js +1 -1
- package/components/badge/badge.css +27 -27
- package/components/badge/badge.js +1 -1
- package/components/block/block.css +16 -16
- package/components/block/block.js +1 -1
- package/components/breadcrumb/breadcrumb.css +23 -23
- package/components/breadcrumb/breadcrumb.js +1 -1
- package/components/button/button.css +101 -91
- package/components/button/button.js +1 -1
- package/components/calendar-grid/calendar-grid.a2ui.json +146 -0
- package/components/calendar-grid/calendar-grid.class.js +326 -0
- package/components/calendar-grid/calendar-grid.css +246 -0
- package/components/calendar-grid/calendar-grid.d.ts +41 -0
- package/components/calendar-grid/calendar-grid.js +17 -0
- package/components/calendar-grid/calendar-grid.yaml +136 -0
- package/components/calendar-picker/calendar-picker.css +139 -139
- package/components/calendar-picker/calendar-picker.js +1 -1
- package/components/canvas/canvas.css +12 -12
- package/components/card/card.css +83 -83
- package/components/card/card.js +1 -1
- package/components/chart/chart.css +224 -224
- package/components/chart/chart.js +1 -1
- package/components/chart-legend/chart-legend.css +26 -26
- package/components/chart-legend/chart-legend.js +1 -1
- package/components/chat-thread/chat-input.a2ui.json +1 -1
- package/components/chat-thread/chat-input.js +6 -1
- package/components/chat-thread/chat-input.yaml +4 -1
- package/components/chat-thread/chat-thread.js +1 -1
- package/components/check/check.css +40 -40
- package/components/check/check.js +1 -1
- package/components/code/code.css +125 -125
- package/components/code/code.js +1 -1
- package/components/col/col.css +15 -15
- package/components/col/col.js +1 -1
- package/components/color-input/color-input.js +1 -1
- package/components/color-picker/color-picker.css +55 -55
- package/components/color-picker/color-picker.js +1 -1
- package/components/combobox/combobox.a2ui.json +363 -0
- package/components/combobox/combobox.class.js +861 -0
- package/components/combobox/combobox.css +244 -0
- package/components/combobox/combobox.d.ts +113 -0
- package/components/combobox/combobox.examples.md +59 -0
- package/components/combobox/combobox.js +17 -0
- package/components/combobox/combobox.test.js +181 -0
- package/components/combobox/combobox.yaml +369 -0
- package/components/command/command.css +90 -90
- package/components/command/command.js +1 -1
- package/components/date-range-picker/date-range-picker.a2ui.json +300 -0
- package/components/date-range-picker/date-range-picker.class.js +791 -0
- package/components/date-range-picker/date-range-picker.css +224 -0
- package/components/date-range-picker/date-range-picker.d.ts +82 -0
- package/components/date-range-picker/date-range-picker.examples.md +37 -0
- package/components/date-range-picker/date-range-picker.js +17 -0
- package/components/date-range-picker/date-range-picker.test.js +387 -0
- package/components/date-range-picker/date-range-picker.yaml +285 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +334 -0
- package/components/datetime-picker/datetime-picker.class.js +706 -0
- package/components/datetime-picker/datetime-picker.css +150 -0
- package/components/datetime-picker/datetime-picker.d.ts +86 -0
- package/components/datetime-picker/datetime-picker.examples.md +46 -0
- package/components/datetime-picker/datetime-picker.js +17 -0
- package/components/datetime-picker/datetime-picker.test.js +454 -0
- package/components/datetime-picker/datetime-picker.yaml +332 -0
- package/components/demo-toggle/demo-toggle.css +27 -27
- package/components/demo-toggle/demo-toggle.js +1 -1
- package/components/description-list/description-list.css +18 -18
- package/components/description-list/description-list.js +1 -1
- package/components/divider/divider.css +24 -24
- package/components/divider/divider.js +1 -1
- package/components/drawer/drawer.js +1 -1
- package/components/embed/embed.css +6 -6
- package/components/embed/embed.js +1 -1
- package/components/empty-state/empty-state.css +27 -27
- package/components/empty-state/empty-state.js +1 -1
- package/components/feed/feed.css +12 -12
- package/components/feed/feed.js +1 -1
- package/components/field/field.css +28 -28
- package/components/field/field.js +1 -1
- package/components/field/field.test.js +1 -1
- package/components/fields/fields.css +5 -5
- package/components/fields/fields.js +1 -1
- package/components/grid/grid.css +5 -5
- package/components/grid/grid.js +1 -1
- package/components/heatmap/heatmap.css +63 -63
- package/components/heatmap/heatmap.js +1 -1
- package/components/icon/icon.css +12 -12
- package/components/icon/icon.js +1 -1
- package/components/image/image.css +14 -14
- package/components/image/image.js +1 -1
- package/components/index.js +11 -0
- package/components/inline-message/inline-message.a2ui.json +143 -0
- package/components/inline-message/inline-message.class.js +169 -0
- package/components/inline-message/inline-message.css +75 -0
- package/components/inline-message/inline-message.d.ts +31 -0
- package/components/inline-message/inline-message.examples.md +19 -0
- package/components/inline-message/inline-message.js +17 -0
- package/components/inline-message/inline-message.test.js +203 -0
- package/components/inline-message/inline-message.yaml +205 -0
- package/components/input/input.css +67 -67
- package/components/input/input.js +1 -1
- package/components/input/input.yaml +5 -4
- package/components/inspector/inspector.css +6 -6
- package/components/inspector/inspector.js +1 -1
- package/components/integration-card/integration-card.a2ui.json +268 -0
- package/components/integration-card/integration-card.class.js +410 -0
- package/components/integration-card/integration-card.css +169 -0
- package/components/integration-card/integration-card.d.ts +63 -0
- package/components/integration-card/integration-card.examples.md +41 -0
- package/components/integration-card/integration-card.js +17 -0
- package/components/integration-card/integration-card.test.js +306 -0
- package/components/integration-card/integration-card.yaml +280 -0
- package/components/kbd/kbd.css +32 -32
- package/components/kbd/kbd.js +1 -1
- package/components/link/link.css +12 -12
- package/components/link/link.js +1 -1
- package/components/list/list-item.yaml +2 -2
- package/components/list/list.css +8 -8
- package/components/list/list.js +1 -1
- package/components/list-window/list-window.a2ui.json +277 -0
- package/components/list-window/list-window.class.js +688 -0
- package/components/list-window/list-window.css +124 -0
- package/components/list-window/list-window.d.ts +84 -0
- package/components/list-window/list-window.examples.md +73 -0
- package/components/list-window/list-window.js +17 -0
- package/components/list-window/list-window.test.js +303 -0
- package/components/list-window/list-window.yaml +270 -0
- package/components/loading-overlay/loading-overlay.a2ui.json +176 -0
- package/components/loading-overlay/loading-overlay.class.js +203 -0
- package/components/loading-overlay/loading-overlay.css +81 -0
- package/components/loading-overlay/loading-overlay.d.ts +24 -0
- package/components/loading-overlay/loading-overlay.examples.md +50 -0
- package/components/loading-overlay/loading-overlay.js +17 -0
- package/components/loading-overlay/loading-overlay.test.js +257 -0
- package/components/loading-overlay/loading-overlay.yaml +260 -0
- package/components/menu/menu-divider.yaml +1 -1
- package/components/menu/menu-item.yaml +1 -1
- package/components/menu/menu.a2ui.json +3 -0
- package/components/menu/menu.css +8 -8
- package/components/menu/menu.js +1 -1
- package/components/menu/menu.yaml +7 -0
- package/components/modal/{class.js → modal.class.js} +12 -1
- package/components/modal/modal.css +54 -44
- package/components/modal/modal.js +1 -1
- package/components/nav/nav.css +40 -40
- package/components/nav/nav.js +1 -1
- package/components/nav-group/nav-group.css +52 -52
- package/components/nav-group/nav-group.js +1 -1
- package/components/nav-item/nav-item.css +44 -44
- package/components/nav-item/nav-item.js +1 -1
- package/components/noodles/noodles.css +31 -31
- package/components/noodles/noodles.js +1 -1
- package/components/option-card/option-card.css +69 -69
- package/components/option-card/option-card.js +1 -1
- package/components/otp-input/otp-input.css +30 -30
- package/components/otp-input/otp-input.js +1 -1
- package/components/page/page.css +18 -18
- package/components/page/page.js +1 -1
- package/components/pagination/pagination.css +61 -61
- package/components/pagination/pagination.js +1 -1
- package/components/pane/pane.css +57 -57
- package/components/pane/pane.js +1 -1
- package/components/pipeline-status/pipeline-status.css +65 -65
- package/components/pipeline-status/pipeline-status.js +1 -1
- package/components/popover/popover.a2ui.json +8 -1
- package/components/popover/popover.css +17 -17
- package/components/popover/popover.js +1 -1
- package/components/popover/popover.yaml +14 -1
- package/components/progress/progress.css +23 -23
- package/components/progress/progress.js +1 -1
- package/components/progress-row/progress-row.css +17 -17
- package/components/progress-row/progress-row.js +1 -1
- package/components/radio/radio.css +39 -39
- package/components/radio/radio.js +1 -1
- package/components/range/range.css +55 -55
- package/components/range/range.js +1 -1
- package/components/rating/rating.css +28 -28
- package/components/rating/rating.js +1 -1
- package/components/richtext/richtext.css +133 -133
- package/components/richtext/richtext.js +1 -1
- package/components/row/row.css +19 -19
- package/components/row/row.js +1 -1
- package/components/search/search.css +5 -5
- package/components/search/search.js +1 -1
- package/components/segment/segment.css +24 -24
- package/components/segment/segment.js +1 -1
- package/components/segmented/segmented.css +25 -25
- package/components/segmented/segmented.js +1 -1
- package/components/select/select.a2ui.json +58 -4
- package/components/select/{class.js → select.class.js} +415 -6
- package/components/select/select.css +242 -84
- package/components/select/select.d.ts +31 -1
- package/components/select/select.js +1 -1
- package/components/select/select.test.js +202 -0
- package/components/select/select.yaml +126 -5
- package/components/skeleton/skeleton.css +14 -14
- package/components/skeleton/skeleton.js +1 -1
- package/components/slider/slider.css +46 -46
- package/components/slider/slider.js +1 -1
- package/components/spinner/spinner.a2ui.json +198 -0
- package/components/spinner/spinner.class.js +99 -0
- package/components/spinner/spinner.css +221 -0
- package/components/spinner/spinner.d.ts +26 -0
- package/components/spinner/spinner.examples.md +26 -0
- package/components/spinner/spinner.js +17 -0
- package/components/spinner/spinner.test.js +272 -0
- package/components/spinner/spinner.yaml +238 -0
- package/components/stack/stack.css +11 -11
- package/components/stack/stack.js +1 -1
- package/components/stat/stat.css +25 -25
- package/components/step-progress/step-progress.css +20 -20
- package/components/step-progress/step-progress.js +1 -1
- package/components/stepper/stepper-item.yaml +1 -1
- package/components/stepper/stepper.css +29 -29
- package/components/stepper/stepper.js +1 -1
- package/components/stream/stream.css +12 -12
- package/components/stream/stream.js +1 -1
- package/components/swatch/swatch.css +68 -68
- package/components/swatch/swatch.js +1 -1
- package/components/swiper/swiper.css +57 -57
- package/components/swiper/swiper.js +1 -1
- package/components/switch/switch.css +52 -52
- package/components/switch/switch.js +1 -1
- package/components/table/table.css +163 -163
- package/components/table/table.js +1 -1
- package/components/table-toolbar/{class.js → table-toolbar.class.js} +1 -1
- package/components/table-toolbar/table-toolbar.css +32 -32
- package/components/table-toolbar/table-toolbar.js +1 -1
- package/components/tabs/tab.yaml +2 -2
- package/components/tabs/tabs.css +51 -51
- package/components/tabs/tabs.js +1 -1
- package/components/tag/tag.css +48 -48
- package/components/tag/tag.js +1 -1
- package/components/tags-input/tags-input.a2ui.json +337 -0
- package/components/tags-input/tags-input.class.js +776 -0
- package/components/tags-input/tags-input.css +201 -0
- package/components/tags-input/tags-input.d.ts +120 -0
- package/components/tags-input/tags-input.examples.md +92 -0
- package/components/tags-input/tags-input.js +17 -0
- package/components/tags-input/tags-input.test.js +368 -0
- package/components/tags-input/tags-input.yaml +367 -0
- package/components/text/text.css +44 -44
- package/components/text/text.js +1 -1
- package/components/textarea/textarea.a2ui.json +1 -1
- package/components/textarea/textarea.css +46 -46
- package/components/textarea/textarea.js +1 -1
- package/components/textarea/textarea.yaml +11 -8
- package/components/time-picker/time-picker.a2ui.json +267 -0
- package/components/time-picker/time-picker.class.js +693 -0
- package/components/time-picker/time-picker.css +122 -0
- package/components/time-picker/time-picker.d.ts +75 -0
- package/components/time-picker/time-picker.examples.md +35 -0
- package/components/time-picker/time-picker.js +17 -0
- package/components/time-picker/time-picker.test.js +287 -0
- package/components/time-picker/time-picker.yaml +256 -0
- package/components/timeline/timeline-item.yaml +2 -2
- package/components/timeline/{class.js → timeline.class.js} +1 -1
- package/components/timeline/timeline.css +50 -50
- package/components/timeline/timeline.js +1 -1
- package/components/toast/toast.css +58 -58
- package/components/toast/toast.js +1 -1
- package/components/toggle-group/toggle-group.css +6 -6
- package/components/toggle-group/toggle-group.js +1 -1
- package/components/toggle-group/toggle-option.yaml +1 -1
- package/components/toggle-scheme/toggle-scheme.css +2 -2
- package/components/toggle-scheme/toggle-scheme.js +1 -1
- package/components/toolbar/toolbar-group.yaml +1 -1
- package/components/toolbar/toolbar.css +17 -17
- package/components/toolbar/toolbar.js +1 -1
- package/components/tooltip/tooltip.css +2 -2
- package/components/tooltip/tooltip.js +1 -1
- package/components/tree/tree-item.yaml +1 -1
- package/components/tree/tree.css +37 -37
- package/components/tree/tree.js +1 -1
- package/components/upload/upload.css +49 -49
- package/components/upload/upload.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +146 -87
- package/package.json +3 -3
- package/styles/components.css +11 -0
- /package/components/accordion/{class.js → accordion.class.js} +0 -0
- /package/components/action-list/{class.js → action-list.class.js} +0 -0
- /package/components/agent-feedback-bar/{class.js → agent-feedback-bar.class.js} +0 -0
- /package/components/agent-questions/{class.js → agent-questions.class.js} +0 -0
- /package/components/agent-reasoning/{class.js → agent-reasoning.class.js} +0 -0
- /package/components/agent-suggestions/{class.js → agent-suggestions.class.js} +0 -0
- /package/components/avatar/{class.js → avatar.class.js} +0 -0
- /package/components/badge/{class.js → badge.class.js} +0 -0
- /package/components/block/{class.js → block.class.js} +0 -0
- /package/components/breadcrumb/{class.js → breadcrumb.class.js} +0 -0
- /package/components/button/{class.js → button.class.js} +0 -0
- /package/components/calendar-picker/{class.js → calendar-picker.class.js} +0 -0
- /package/components/card/{class.js → card.class.js} +0 -0
- /package/components/chart/{class.js → chart.class.js} +0 -0
- /package/components/chart-legend/{class.js → chart-legend.class.js} +0 -0
- /package/components/chat-thread/{class.js → chat-thread.class.js} +0 -0
- /package/components/check/{class.js → check.class.js} +0 -0
- /package/components/code/{class.js → code.class.js} +0 -0
- /package/components/col/{class.js → col.class.js} +0 -0
- /package/components/color-input/{class.js → color-input.class.js} +0 -0
- /package/components/color-picker/{class.js → color-picker.class.js} +0 -0
- /package/components/command/{class.js → command.class.js} +0 -0
- /package/components/demo-toggle/{class.js → demo-toggle.class.js} +0 -0
- /package/components/description-list/{class.js → description-list.class.js} +0 -0
- /package/components/divider/{class.js → divider.class.js} +0 -0
- /package/components/drawer/{class.js → drawer.class.js} +0 -0
- /package/components/embed/{class.js → embed.class.js} +0 -0
- /package/components/empty-state/{class.js → empty-state.class.js} +0 -0
- /package/components/feed/{class.js → feed.class.js} +0 -0
- /package/components/field/{class.js → field.class.js} +0 -0
- /package/components/fields/{class.js → fields.class.js} +0 -0
- /package/components/grid/{class.js → grid.class.js} +0 -0
- /package/components/heatmap/{class.js → heatmap.class.js} +0 -0
- /package/components/icon/{class.js → icon.class.js} +0 -0
- /package/components/image/{class.js → image.class.js} +0 -0
- /package/components/input/{class.js → input.class.js} +0 -0
- /package/components/inspector/{class.js → inspector.class.js} +0 -0
- /package/components/kbd/{class.js → kbd.class.js} +0 -0
- /package/components/link/{class.js → link.class.js} +0 -0
- /package/components/list/{class.js → list.class.js} +0 -0
- /package/components/menu/{class.js → menu.class.js} +0 -0
- /package/components/nav/{class.js → nav.class.js} +0 -0
- /package/components/nav-group/{class.js → nav-group.class.js} +0 -0
- /package/components/nav-item/{class.js → nav-item.class.js} +0 -0
- /package/components/noodles/{class.js → noodles.class.js} +0 -0
- /package/components/option-card/{class.js → option-card.class.js} +0 -0
- /package/components/otp-input/{class.js → otp-input.class.js} +0 -0
- /package/components/page/{class.js → page.class.js} +0 -0
- /package/components/pagination/{class.js → pagination.class.js} +0 -0
- /package/components/pane/{class.js → pane.class.js} +0 -0
- /package/components/pipeline-status/{class.js → pipeline-status.class.js} +0 -0
- /package/components/popover/{class.js → popover.class.js} +0 -0
- /package/components/progress/{class.js → progress.class.js} +0 -0
- /package/components/progress-row/{class.js → progress-row.class.js} +0 -0
- /package/components/radio/{class.js → radio.class.js} +0 -0
- /package/components/range/{class.js → range.class.js} +0 -0
- /package/components/rating/{class.js → rating.class.js} +0 -0
- /package/components/richtext/{class.js → richtext.class.js} +0 -0
- /package/components/row/{class.js → row.class.js} +0 -0
- /package/components/search/{class.js → search.class.js} +0 -0
- /package/components/segment/{class.js → segment.class.js} +0 -0
- /package/components/segmented/{class.js → segmented.class.js} +0 -0
- /package/components/skeleton/{class.js → skeleton.class.js} +0 -0
- /package/components/slider/{class.js → slider.class.js} +0 -0
- /package/components/stack/{class.js → stack.class.js} +0 -0
- /package/components/step-progress/{class.js → step-progress.class.js} +0 -0
- /package/components/stepper/{class.js → stepper.class.js} +0 -0
- /package/components/stream/{class.js → stream.class.js} +0 -0
- /package/components/swatch/{class.js → swatch.class.js} +0 -0
- /package/components/swiper/{class.js → swiper.class.js} +0 -0
- /package/components/switch/{class.js → switch.class.js} +0 -0
- /package/components/table/{class.js → table.class.js} +0 -0
- /package/components/tabs/{class.js → tabs.class.js} +0 -0
- /package/components/tag/{class.js → tag.class.js} +0 -0
- /package/components/text/{class.js → text.class.js} +0 -0
- /package/components/textarea/{class.js → textarea.class.js} +0 -0
- /package/components/toast/{class.js → toast.class.js} +0 -0
- /package/components/toggle-group/{class.js → toggle-group.class.js} +0 -0
- /package/components/toggle-scheme/{class.js → toggle-scheme.class.js} +0 -0
- /package/components/toolbar/{class.js → toolbar.class.js} +0 -0
- /package/components/tooltip/{class.js → tooltip.class.js} +0 -0
- /package/components/tree/{class.js → tree.class.js} +0 -0
- /package/components/upload/{class.js → upload.class.js} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Authored 2026-05-23 for SPEC-022 (Virtualized / Windowed List). The
|
|
2
|
+
# component name in catalog metadata reads "Virtualized Windowed List"
|
|
3
|
+
# for retrieval / searchability; the tag is the shorter `list-window-ui`
|
|
4
|
+
# which lives alongside `<list-ui>` / `<list-item-ui>` in the list-*
|
|
5
|
+
# namespace.
|
|
6
|
+
#
|
|
7
|
+
# Edit this file; run `npm run build:components` to regenerate the
|
|
8
|
+
# `list-window.a2ui.json` sidecar. Never hand-edit the .a2ui.json.
|
|
9
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
10
|
+
name: UIListWindow
|
|
11
|
+
tag: list-window-ui
|
|
12
|
+
status: stable
|
|
13
|
+
component: ListWindow
|
|
14
|
+
category: display
|
|
15
|
+
version: 1
|
|
16
|
+
description: >-
|
|
17
|
+
Virtualized / windowed list primitive. Renders only the visible slice
|
|
18
|
+
of a large items[] array (chat threads, feeds, log streams, nav lists,
|
|
19
|
+
search-result panes) — typically ≤50 DOM rows regardless of the
|
|
20
|
+
underlying collection size. Composes a `render`-function prop OR a
|
|
21
|
+
slotted <template> for row materialization; ships a fixed-size
|
|
22
|
+
fast-path (constant-time index→offset math) and a variable-size
|
|
23
|
+
measurement fallback. Distinct from <list-ui> (renders every child,
|
|
24
|
+
preferred for short lists < 50 items) and <table-ui> (tabular data
|
|
25
|
+
with columns). Use list-window-ui when items.length is large enough
|
|
26
|
+
that rendering every row would block the main thread or stutter
|
|
27
|
+
scroll.
|
|
28
|
+
# Per ADR-0027 — primitives that programmatically create other primitives
|
|
29
|
+
# do NOT auto-import them. Consumer (or demo shell) must explicitly import.
|
|
30
|
+
composes:
|
|
31
|
+
- skeleton-ui
|
|
32
|
+
props:
|
|
33
|
+
items:
|
|
34
|
+
description: The items to virtualize. Required for prop-driven authoring; ignored when data-stream-src is set.
|
|
35
|
+
type: array
|
|
36
|
+
default: []
|
|
37
|
+
itemSize:
|
|
38
|
+
description: Fixed item height in pixels. When > 0, uses the constant-time fast path (avoids per-row measurement).
|
|
39
|
+
type: number
|
|
40
|
+
default: 0
|
|
41
|
+
reflect: true
|
|
42
|
+
attribute: item-size
|
|
43
|
+
itemSizeRem:
|
|
44
|
+
description: Fixed item height in rem. Useful for typographic-scale rows that should track the body type.
|
|
45
|
+
type: number
|
|
46
|
+
default: 0
|
|
47
|
+
reflect: true
|
|
48
|
+
attribute: item-size-rem
|
|
49
|
+
estimatedSize:
|
|
50
|
+
description: Initial guess for variable-height rows. Used until the first measurement pass refines the offset cache.
|
|
51
|
+
type: number
|
|
52
|
+
default: 48
|
|
53
|
+
reflect: true
|
|
54
|
+
attribute: estimated-size
|
|
55
|
+
overscan:
|
|
56
|
+
description: >-
|
|
57
|
+
Rows to render above + below the visible window. 0–20 is reasonable; > 50 negates the windowing benefit.
|
|
58
|
+
type: number
|
|
59
|
+
default: 3
|
|
60
|
+
reflect: true
|
|
61
|
+
direction:
|
|
62
|
+
description: Scroll axis — vertical (default) or horizontal carousel.
|
|
63
|
+
type: string
|
|
64
|
+
default: vertical
|
|
65
|
+
enum:
|
|
66
|
+
- vertical
|
|
67
|
+
- horizontal
|
|
68
|
+
reflect: true
|
|
69
|
+
pinBottom:
|
|
70
|
+
description: When appending items, keep scroll pinned to the bottom (chat-thread / log-tail pattern).
|
|
71
|
+
type: boolean
|
|
72
|
+
default: false
|
|
73
|
+
reflect: true
|
|
74
|
+
attribute: pin-bottom
|
|
75
|
+
startIndex:
|
|
76
|
+
description: Index to scroll to on mount. Useful for restoring scroll position on remount.
|
|
77
|
+
type: number
|
|
78
|
+
default: 0
|
|
79
|
+
attribute: start-index
|
|
80
|
+
loading:
|
|
81
|
+
description: Render skeleton rows in the visible window. Sets aria-busy="true" on the host.
|
|
82
|
+
type: boolean
|
|
83
|
+
default: false
|
|
84
|
+
reflect: true
|
|
85
|
+
events:
|
|
86
|
+
range-change:
|
|
87
|
+
description: Fired when the visible row range (start/end indices) changes due to scroll.
|
|
88
|
+
detail:
|
|
89
|
+
startIndex:
|
|
90
|
+
type: number
|
|
91
|
+
description: First rendered row index.
|
|
92
|
+
endIndex:
|
|
93
|
+
type: number
|
|
94
|
+
description: Last rendered row index (exclusive).
|
|
95
|
+
items:
|
|
96
|
+
type: array
|
|
97
|
+
description: The items currently materialized in the window.
|
|
98
|
+
item-click:
|
|
99
|
+
description: Fired when a rendered row is clicked.
|
|
100
|
+
detail:
|
|
101
|
+
item:
|
|
102
|
+
description: The clicked item (full item-shape from items[]).
|
|
103
|
+
index:
|
|
104
|
+
type: number
|
|
105
|
+
description: Item index in the full items[] array.
|
|
106
|
+
scroll-end:
|
|
107
|
+
description: Fired when the user scrolls to the bottom (within 1 viewport). Use for infinite-load patterns.
|
|
108
|
+
detail:
|
|
109
|
+
index:
|
|
110
|
+
type: number
|
|
111
|
+
description: Last visible row index.
|
|
112
|
+
scroll-start:
|
|
113
|
+
description: Fired when the user scrolls to the top (within 1 viewport). Use for "load older" patterns.
|
|
114
|
+
detail:
|
|
115
|
+
index:
|
|
116
|
+
type: number
|
|
117
|
+
description: First visible row index.
|
|
118
|
+
measure:
|
|
119
|
+
description: Fired when a variable-height row is measured. Useful for instrumenting the offset cache.
|
|
120
|
+
detail:
|
|
121
|
+
index:
|
|
122
|
+
type: number
|
|
123
|
+
description: Index of the row that was measured.
|
|
124
|
+
height:
|
|
125
|
+
type: number
|
|
126
|
+
description: Measured row height in pixels.
|
|
127
|
+
slots:
|
|
128
|
+
default:
|
|
129
|
+
description: A single <template> element used to clone rows (declarative-template authoring). Mutually exclusive with the render prop.
|
|
130
|
+
empty:
|
|
131
|
+
description: Custom empty-state content when items.length === 0.
|
|
132
|
+
loading:
|
|
133
|
+
description: Custom skeleton row template; falls back to <skeleton-ui> when omitted.
|
|
134
|
+
before:
|
|
135
|
+
description: Sticky-top content (filter chips, summary stat).
|
|
136
|
+
after:
|
|
137
|
+
description: Sticky-bottom content (composer, "load older" button).
|
|
138
|
+
states:
|
|
139
|
+
- name: idle
|
|
140
|
+
description: Default — rendering and reconciling normally.
|
|
141
|
+
- name: loading
|
|
142
|
+
attribute: loading
|
|
143
|
+
description: Skeleton rows; data fetch in flight.
|
|
144
|
+
- name: empty
|
|
145
|
+
attribute: empty
|
|
146
|
+
description: items.length === 0.
|
|
147
|
+
- name: measuring
|
|
148
|
+
attribute: measuring
|
|
149
|
+
description: First-mount measurement pass on variable-height rows; suppresses scroll-end events.
|
|
150
|
+
- name: disabled
|
|
151
|
+
attribute: disabled
|
|
152
|
+
description: Pointer events blocked.
|
|
153
|
+
traits: []
|
|
154
|
+
tokens:
|
|
155
|
+
--list-window-bg:
|
|
156
|
+
description: Host background (defaults to --a-bg).
|
|
157
|
+
--list-window-row-gap:
|
|
158
|
+
description: Row spacing in the visible window (defaults to --a-space-1).
|
|
159
|
+
--list-window-overscan-bg:
|
|
160
|
+
description: Visible buffer rows background — transparent by default.
|
|
161
|
+
--list-window-sentinel-size:
|
|
162
|
+
description: Top + bottom IntersectionObserver-target sentinel size (defaults to --a-space-2).
|
|
163
|
+
keywords:
|
|
164
|
+
- list-window
|
|
165
|
+
- virtualized
|
|
166
|
+
- windowed
|
|
167
|
+
- virtual-scroll
|
|
168
|
+
- infinite-scroll
|
|
169
|
+
- large-list
|
|
170
|
+
- feed
|
|
171
|
+
- chat-thread
|
|
172
|
+
- log-stream
|
|
173
|
+
- 10k-rows
|
|
174
|
+
synonyms:
|
|
175
|
+
virtualized:
|
|
176
|
+
- virtual-scroll
|
|
177
|
+
- windowed
|
|
178
|
+
- list-window
|
|
179
|
+
windowed:
|
|
180
|
+
- virtualized
|
|
181
|
+
- virtual-scroll
|
|
182
|
+
- list-window
|
|
183
|
+
virtual-scroll:
|
|
184
|
+
- virtualized
|
|
185
|
+
- windowed
|
|
186
|
+
- list-window
|
|
187
|
+
infinite-scroll:
|
|
188
|
+
- virtualized
|
|
189
|
+
- virtual-scroll
|
|
190
|
+
- list-window
|
|
191
|
+
large-list:
|
|
192
|
+
- virtualized
|
|
193
|
+
- list-window
|
|
194
|
+
related:
|
|
195
|
+
- List
|
|
196
|
+
- ListItem
|
|
197
|
+
- Feed
|
|
198
|
+
- ChatThread
|
|
199
|
+
- Table
|
|
200
|
+
a2ui:
|
|
201
|
+
rules:
|
|
202
|
+
- rule: 'ListWindow.items MUST be an array of plain objects OR scalars. Functions / DOM nodes / Promises are invalid.'
|
|
203
|
+
reason: 'Items are reconciled via key-fn + serialized to row DOM; non-data values cannot survive the round-trip.'
|
|
204
|
+
- rule: 'ListWindow.render cannot be expressed in A2UI JSON — declarative authoring MUST use a <template> child (or default <list-item-ui> for objects with a text field).'
|
|
205
|
+
reason: 'A2UI is a JSON catalog; function values have no transport.'
|
|
206
|
+
- rule: 'When items.length > 200, the validator SHOULD recommend ListWindow over List to keep the DOM tractable.'
|
|
207
|
+
reason: 'List renders every item; at large N the cost is super-linear in layout / paint / memory.'
|
|
208
|
+
- rule: 'ListWindow MUST have a defined height (via parent layout or style="height:..."). An unbounded-height windowed list defeats the windowing math.'
|
|
209
|
+
reason: 'Without a viewport bound the scroll container has no visible-window size; every item would mount.'
|
|
210
|
+
- rule: 'ListWindow.item-size SHOULD be set when item heights are known and constant — the fast-path is significantly cheaper.'
|
|
211
|
+
reason: 'Constant-time index→offset math beats per-row measurement.'
|
|
212
|
+
- rule: 'Do NOT nest ListWindow inside another scroll container; double-scroll containers break the IntersectionObserver math. Use one scroll boundary.'
|
|
213
|
+
reason: 'Nested scroll containers create ambiguous visible-window targets.'
|
|
214
|
+
- rule: 'Do NOT use ListWindow for short lists (< 50 items). The windowing overhead exceeds the cost of rendering all rows. Use List for short lists.'
|
|
215
|
+
reason: 'Below the windowing threshold the bookkeeping is pure cost.'
|
|
216
|
+
- rule: 'Do NOT use for tabular data — that is Table with virtualized rows.'
|
|
217
|
+
reason: 'Different surface; Table owns columns + grid roles.'
|
|
218
|
+
anti_patterns:
|
|
219
|
+
- wrong: |
|
|
220
|
+
{ "component": "List", "items": [/* 10000 items */] }
|
|
221
|
+
why: |
|
|
222
|
+
Rendering 10k items into <list-ui> blows up the DOM and main thread. Scroll
|
|
223
|
+
jank, mount lag, and memory pressure all degrade.
|
|
224
|
+
fix: |
|
|
225
|
+
{ "component": "ListWindow", "items": [/* 10000 items */], "item-size": 48 }
|
|
226
|
+
- wrong: |
|
|
227
|
+
{ "component": "ListWindow", "items": [/* … */] }
|
|
228
|
+
(with no parent height + no item-size + no estimated-size)
|
|
229
|
+
why: |
|
|
230
|
+
Without a height bound, the scroll container has no viewport, so the windowing
|
|
231
|
+
math reports "all items visible" and the whole list mounts.
|
|
232
|
+
fix: |
|
|
233
|
+
Wrap in a container with a height, or set style="height:480px" on the
|
|
234
|
+
ListWindow itself.
|
|
235
|
+
- wrong: |
|
|
236
|
+
{ "component": "ListWindow", "overscan": 200, "items": [/* … */] }
|
|
237
|
+
why: |
|
|
238
|
+
Overscan=200 materializes 200 rows above + below the viewport. That defeats
|
|
239
|
+
the entire point of windowing.
|
|
240
|
+
fix: |
|
|
241
|
+
{ "component": "ListWindow", "overscan": 5, "items": [/* … */] }
|
|
242
|
+
examples:
|
|
243
|
+
- name: chat-thread-list
|
|
244
|
+
description: Virtualized chat-thread message list with declarative <template> rows.
|
|
245
|
+
a2ui: >-
|
|
246
|
+
[
|
|
247
|
+
{
|
|
248
|
+
"id": "root",
|
|
249
|
+
"component": "Card",
|
|
250
|
+
"children": ["thread"]
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": "thread",
|
|
254
|
+
"component": "ListWindow",
|
|
255
|
+
"item-size": 56,
|
|
256
|
+
"overscan": 5,
|
|
257
|
+
"pin-bottom": true
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
- name: log-tail-stream
|
|
261
|
+
description: SSE-streamed JSONL log tail with sticky-bottom pin.
|
|
262
|
+
a2ui: >-
|
|
263
|
+
[
|
|
264
|
+
{
|
|
265
|
+
"id": "logs",
|
|
266
|
+
"component": "ListWindow",
|
|
267
|
+
"item-size": 24,
|
|
268
|
+
"pin-bottom": true
|
|
269
|
+
}
|
|
270
|
+
]
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/LoadingOverlay.json",
|
|
4
|
+
"title": "LoadingOverlay",
|
|
5
|
+
"description": "Container-scoped busy overlay. Covers a positioned parent region with a centered spinner (auto-stamped) or slotted indicator (skeleton-ui, progress-ui, custom) while async work is in flight. Wires aria-busy onto the parent on connect; applies a [delay] grace window to avoid flash on fast loads. For viewport-scoped / route loaders use a dedicated route-loader pattern; for submit-button busy use <button-ui loading>.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"active": {
|
|
17
|
+
"description": "When set, overlay is visible and the parent container is marked aria-busy. Toggle from consumer code; default hidden.",
|
|
18
|
+
"$ref": "common_types.json#/$defs/DynamicBoolean"
|
|
19
|
+
},
|
|
20
|
+
"component": {
|
|
21
|
+
"const": "LoadingOverlay"
|
|
22
|
+
},
|
|
23
|
+
"delay": {
|
|
24
|
+
"description": "Suppress the overlay until this many ms elapse. Prevents flash on fast loads. Default 200ms.",
|
|
25
|
+
"$ref": "common_types.json#/$defs/DynamicNumber"
|
|
26
|
+
},
|
|
27
|
+
"label": {
|
|
28
|
+
"description": "Accessible operation name used by the host aria-label and forwarded to the auto-stamped spinner.",
|
|
29
|
+
"$ref": "common_types.json#/$defs/DynamicString"
|
|
30
|
+
},
|
|
31
|
+
"variant": {
|
|
32
|
+
"description": "Backdrop treatment — default (muted scrim), transparent (no backdrop fill — useful with full-area skeleton indicators), blur (light scrim with backdrop-filter blur).",
|
|
33
|
+
"$ref": "common_types.json#/$defs/DynamicString"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"required": [
|
|
37
|
+
"component"
|
|
38
|
+
],
|
|
39
|
+
"unevaluatedProperties": false,
|
|
40
|
+
"x-adiaui": {
|
|
41
|
+
"anti_patterns": [
|
|
42
|
+
{
|
|
43
|
+
"fix": "{\"component\": \"Card\", \"children\": [\n {\"component\": \"LoadingOverlay\", \"active\": true},\n {\"component\": \"Table\", \"data-stream-src\": \"...\"}\n]}\n",
|
|
44
|
+
"why": "LoadingOverlay is container-scoped. Attaching it directly to Page\ncovers the entire viewport but lacks the route-loader's wiring\n(skeleton routing, route-level animation). Use the route loader.\n",
|
|
45
|
+
"wrong": "{\"component\": \"Page\", \"children\": [\n {\"component\": \"LoadingOverlay\", \"active\": true}\n]}\n"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"fix": "{\"component\": \"Modal\", \"open\": true, \"loading\": true}\n",
|
|
49
|
+
"why": "Modal owns its own busy state and focus trap; nesting a\nLoadingOverlay introduces two competing inert toggles.\n",
|
|
50
|
+
"wrong": "{\"component\": \"Modal\", \"open\": true, \"children\": [\n {\"component\": \"LoadingOverlay\", \"active\": true}\n]}\n"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"fix": "{\"component\": \"Form\", \"children\": [\n {\"component\": \"Button\", \"text\": \"Submitting\", \"type\": \"submit\", \"loading\": true, \"disabled\": true}\n]}\n",
|
|
54
|
+
"why": "A form-submit affordance belongs on the button itself, not as an\noverlay over the entire form region. Use Button[loading].\n",
|
|
55
|
+
"wrong": "{\"component\": \"Form\", \"children\": [\n {\"component\": \"Button\", \"text\": \"Submit\", \"type\": \"submit\"},\n {\"component\": \"LoadingOverlay\", \"active\": true}\n]}\n"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"category": "feedback",
|
|
59
|
+
"composes": [],
|
|
60
|
+
"events": {},
|
|
61
|
+
"examples": [
|
|
62
|
+
{
|
|
63
|
+
"description": "Card containing a table that loads on mount; the overlay covers the card's body while data streams.",
|
|
64
|
+
"a2ui": "[\n {\n \"id\": \"card\",\n \"component\": \"Card\",\n \"children\": [\"sec\"]\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\"overlay\", \"table\"]\n },\n {\n \"id\": \"overlay\",\n \"component\": \"LoadingOverlay\",\n \"active\": true,\n \"label\": \"Loading orders…\"\n },\n {\n \"id\": \"table\",\n \"component\": \"Table\",\n \"data-stream-src\": \"/api/orders\"\n }\n]",
|
|
65
|
+
"name": "card-with-loading-table"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"description": "Skeleton-shaped placeholder overlay using the transparent variant — slot a stack of <Skeleton> rows.",
|
|
69
|
+
"a2ui": "[\n {\n \"id\": \"ov\",\n \"component\": \"LoadingOverlay\",\n \"active\": true,\n \"variant\": \"transparent\",\n \"label\": \"Loading content…\",\n \"children\": [\"sk-stack\"]\n },\n {\n \"id\": \"sk-stack\",\n \"component\": \"Column\",\n \"gap\": \"2\",\n \"children\": [\"sk1\", \"sk2\", \"sk3\"]\n },\n {\n \"id\": \"sk1\",\n \"component\": \"Skeleton\",\n \"width\": \"100%\",\n \"height\": \"1rem\"\n },\n {\n \"id\": \"sk2\",\n \"component\": \"Skeleton\",\n \"width\": \"80%\",\n \"height\": \"1rem\"\n },\n {\n \"id\": \"sk3\",\n \"component\": \"Skeleton\",\n \"width\": \"60%\",\n \"height\": \"1rem\"\n }\n]",
|
|
70
|
+
"name": "skeleton-overlay"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"description": "Overlay with the default 200ms grace window. If the underlying load resolves in less than 200ms, the overlay never paints.",
|
|
74
|
+
"a2ui": "[\n {\n \"id\": \"ov\",\n \"component\": \"LoadingOverlay\",\n \"active\": true,\n \"delay\": 200,\n \"label\": \"Loading\"\n }\n]",
|
|
75
|
+
"name": "fast-load-no-flash"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"keywords": [
|
|
79
|
+
"loading",
|
|
80
|
+
"overlay",
|
|
81
|
+
"busy",
|
|
82
|
+
"spinner",
|
|
83
|
+
"skeleton",
|
|
84
|
+
"placeholder",
|
|
85
|
+
"feedback",
|
|
86
|
+
"progress",
|
|
87
|
+
"indicator",
|
|
88
|
+
"wait"
|
|
89
|
+
],
|
|
90
|
+
"name": "UILoadingOverlay",
|
|
91
|
+
"related": [
|
|
92
|
+
"Spinner",
|
|
93
|
+
"Skeleton",
|
|
94
|
+
"Progress",
|
|
95
|
+
"Card",
|
|
96
|
+
"Table",
|
|
97
|
+
"Modal"
|
|
98
|
+
],
|
|
99
|
+
"slots": {
|
|
100
|
+
"default": {
|
|
101
|
+
"description": "Busy indicator content — <spinner-ui>, <skeleton-ui>, <progress-ui>, or custom. If empty, a centered <spinner-ui size=\"lg\"> is auto-stamped."
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"states": [
|
|
105
|
+
{
|
|
106
|
+
"description": "Default, hidden. Parent interactive.",
|
|
107
|
+
"name": "idle"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"description": "Overlay visible; parent has aria-busy=\"true\".",
|
|
111
|
+
"attribute": "active",
|
|
112
|
+
"name": "active"
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"status": "stable",
|
|
116
|
+
"synonyms": {
|
|
117
|
+
"busy": [
|
|
118
|
+
"loading",
|
|
119
|
+
"overlay",
|
|
120
|
+
"busy",
|
|
121
|
+
"spinner"
|
|
122
|
+
],
|
|
123
|
+
"loader": [
|
|
124
|
+
"loading",
|
|
125
|
+
"overlay",
|
|
126
|
+
"spinner"
|
|
127
|
+
],
|
|
128
|
+
"loading": [
|
|
129
|
+
"loading",
|
|
130
|
+
"overlay",
|
|
131
|
+
"busy",
|
|
132
|
+
"spinner",
|
|
133
|
+
"skeleton"
|
|
134
|
+
],
|
|
135
|
+
"overlay": [
|
|
136
|
+
"loading",
|
|
137
|
+
"overlay",
|
|
138
|
+
"busy"
|
|
139
|
+
],
|
|
140
|
+
"spinner": [
|
|
141
|
+
"loading",
|
|
142
|
+
"overlay",
|
|
143
|
+
"spinner"
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
"tag": "loading-overlay-ui",
|
|
147
|
+
"tokens": {
|
|
148
|
+
"--loading-overlay-bg": {
|
|
149
|
+
"description": "Backdrop fill color. Default uses the generic neutral scrim token.",
|
|
150
|
+
"default": "var(--a-scrim-default)"
|
|
151
|
+
},
|
|
152
|
+
"--loading-overlay-duration": {
|
|
153
|
+
"description": "Fade in / fade out duration.",
|
|
154
|
+
"default": "var(--a-duration)"
|
|
155
|
+
},
|
|
156
|
+
"--loading-overlay-easing": {
|
|
157
|
+
"description": "Fade easing curve.",
|
|
158
|
+
"default": "var(--a-easing-out)"
|
|
159
|
+
},
|
|
160
|
+
"--loading-overlay-gap": {
|
|
161
|
+
"description": "Vertical gap between the indicator and any sibling content (e.g. label, skeleton stack).",
|
|
162
|
+
"default": "var(--a-space-3)"
|
|
163
|
+
},
|
|
164
|
+
"--loading-overlay-radius": {
|
|
165
|
+
"description": "Backdrop corner radius. Matches the parent surface by default.",
|
|
166
|
+
"default": "var(--a-radius-md)"
|
|
167
|
+
},
|
|
168
|
+
"--loading-overlay-z": {
|
|
169
|
+
"description": "Stacking order. Sits above sibling content, below modal-ui (which uses ::backdrop on top-layer dialog).",
|
|
170
|
+
"default": "50"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"traits": [],
|
|
174
|
+
"version": 1
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<loading-overlay-ui>`.
|
|
3
|
+
*
|
|
4
|
+
* Importing this file gives you the class without auto-registering the
|
|
5
|
+
* tag. Useful for test isolation, subclassing with tag-name override, or
|
|
6
|
+
* selective composition.
|
|
7
|
+
*
|
|
8
|
+
* The auto-register path stays at `@adia-ai/web-components/components/loading-overlay`
|
|
9
|
+
* (which imports this file + calls `defineIfFree()`).
|
|
10
|
+
*
|
|
11
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* <loading-overlay-ui active label="Loading orders…"></loading-overlay-ui>
|
|
16
|
+
* <loading-overlay-ui active variant="transparent" delay="0">
|
|
17
|
+
* <skeleton-ui width="100%" height="1rem"></skeleton-ui>
|
|
18
|
+
* <skeleton-ui width="80%" height="1rem"></skeleton-ui>
|
|
19
|
+
* </loading-overlay-ui>
|
|
20
|
+
*
|
|
21
|
+
* Container-scoped busy overlay. Covers a positioned parent region with
|
|
22
|
+
* a spinner, skeleton, or custom busy indicator while async work is in
|
|
23
|
+
* flight. The host:
|
|
24
|
+
*
|
|
25
|
+
* 1. Absolutely positions to fill the offsetParent (consumer's
|
|
26
|
+
* responsibility to ensure that parent is `position: relative` or
|
|
27
|
+
* similar — documented in the spec / usage notes; we do NOT mutate
|
|
28
|
+
* parent layout styles).
|
|
29
|
+
* 2. Applies aria-busy="true" to the parent on activate; releases on
|
|
30
|
+
* deactivate / disconnect. This is screen-reader politeness; the
|
|
31
|
+
* backdrop's pointer-events:auto handles the click/focus block at
|
|
32
|
+
* the CSS level (no native [inert] attribute toggling — that's
|
|
33
|
+
* kept minimal to avoid disturbing the consumer's focus model).
|
|
34
|
+
* 3. Auto-stamps a centered <spinner-ui size="lg"> when the default
|
|
35
|
+
* slot is empty.
|
|
36
|
+
* 4. Honors a `[delay]` grace window (default 200ms) — if the active
|
|
37
|
+
* flag is cleared before the timer fires, the overlay never
|
|
38
|
+
* paints. Avoids flash on fast loads.
|
|
39
|
+
*
|
|
40
|
+
* Props (attributes):
|
|
41
|
+
* active — overlay visible (default: false)
|
|
42
|
+
* delay — grace window in ms before paint (default: 200)
|
|
43
|
+
* label — accessible label, also forwarded to auto-stamped spinner
|
|
44
|
+
* variant — default | transparent | blur
|
|
45
|
+
*
|
|
46
|
+
* Lifecycle: connected() seeds aria-label on host + caches parent
|
|
47
|
+
* reference for aria-busy bookkeeping. Disconnected() clears any
|
|
48
|
+
* pending delay timer and releases aria-busy on the parent. State
|
|
49
|
+
* changes are driven by the reactive [active] setter wrapper, which
|
|
50
|
+
* starts/cancels the timer + applies aria-busy when the timer fires.
|
|
51
|
+
*
|
|
52
|
+
* @see SPEC-034 (docs/specs/implementation-ready/SPEC-034-loading-overlay.md)
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
import { UIElement } from '../../core/element.js';
|
|
56
|
+
|
|
57
|
+
export class UILoadingOverlay extends UIElement {
|
|
58
|
+
static properties = {
|
|
59
|
+
active: { type: Boolean, default: false, reflect: true },
|
|
60
|
+
delay: { type: Number, default: 200, reflect: true },
|
|
61
|
+
label: { type: String, default: 'Loading…', reflect: true },
|
|
62
|
+
variant: { type: String, default: 'default', reflect: true },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// No stamped children authored by the template engine; auto-stamp of
|
|
66
|
+
// the default spinner happens imperatively in connected() / render()
|
|
67
|
+
// when the slot is empty. Authored children (skeleton/progress/etc.)
|
|
68
|
+
// are left in place per ADR-0033 (light-DOM substrate).
|
|
69
|
+
static template = () => null;
|
|
70
|
+
|
|
71
|
+
#parent = null;
|
|
72
|
+
#delayTimer = null;
|
|
73
|
+
#activeApplied = false; // tracks whether aria-busy / opacity actually painted
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
super();
|
|
77
|
+
// Wrap the auto-installed `active` setter so state transitions kick
|
|
78
|
+
// the delay timer synchronously with the assignment — matching the
|
|
79
|
+
// drawer-ui Safari pattern (avoids microtask scheduling delays).
|
|
80
|
+
const desc = Object.getOwnPropertyDescriptor(this, 'active');
|
|
81
|
+
if (desc?.set) {
|
|
82
|
+
const origSet = desc.set;
|
|
83
|
+
Object.defineProperty(this, 'active', {
|
|
84
|
+
get: desc.get,
|
|
85
|
+
set: (v) => {
|
|
86
|
+
const prev = !!desc.get.call(this);
|
|
87
|
+
origSet.call(this, v);
|
|
88
|
+
if (!!v !== prev) this.#syncActive(!!v);
|
|
89
|
+
},
|
|
90
|
+
configurable: true,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
connected() {
|
|
96
|
+
// Host ARIA: role=status is a polite live region; aria-label
|
|
97
|
+
// names the operation. Both are seeded once; render() keeps the
|
|
98
|
+
// label fresh on prop changes.
|
|
99
|
+
if (!this.hasAttribute('role')) this.setAttribute('role', 'status');
|
|
100
|
+
if (!this.hasAttribute('aria-live')) this.setAttribute('aria-live', 'polite');
|
|
101
|
+
this.setAttribute('aria-label', this.label || 'Loading');
|
|
102
|
+
|
|
103
|
+
// Cache the parent so disconnected() can release aria-busy even if
|
|
104
|
+
// the element is detached before parentElement is re-readable.
|
|
105
|
+
this.#parent = this.parentElement;
|
|
106
|
+
|
|
107
|
+
// If the element mounted with [active] already set (declarative),
|
|
108
|
+
// honor it. The reactive setter wrapper only fires on subsequent
|
|
109
|
+
// assignments, so we kick the timer here too.
|
|
110
|
+
if (this.active) this.#syncActive(true);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
disconnected() {
|
|
114
|
+
// Symmetric lifecycle: clear any in-flight timer + release aria-busy
|
|
115
|
+
// on the parent we cached at connect time.
|
|
116
|
+
this.#clearTimer();
|
|
117
|
+
if (this.#activeApplied) this.#releaseBusy();
|
|
118
|
+
this.#parent = null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
render() {
|
|
122
|
+
// Keep the host label fresh if [label] is mutated after connect.
|
|
123
|
+
// Forward it to an auto-stamped spinner-ui (if present) so screen
|
|
124
|
+
// readers hear a single coherent name.
|
|
125
|
+
const label = this.label || 'Loading';
|
|
126
|
+
this.setAttribute('aria-label', label);
|
|
127
|
+
|
|
128
|
+
const ownSpinner = this.querySelector(':scope > spinner-ui[data-loading-overlay-auto]');
|
|
129
|
+
if (ownSpinner && ownSpinner.getAttribute('label') !== label) {
|
|
130
|
+
ownSpinner.setAttribute('label', label);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Ensure the auto-stamped spinner exists when the slot is empty.
|
|
134
|
+
// Authored children (consumer-supplied indicators) win; we only
|
|
135
|
+
// stamp when there are no element children at all.
|
|
136
|
+
if (this.children.length === 0) {
|
|
137
|
+
this.#stampDefaultSpinner();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Internals ───────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
#syncActive(next) {
|
|
144
|
+
// Clear any prior in-flight timer first; the new state owns the
|
|
145
|
+
// grace window.
|
|
146
|
+
this.#clearTimer();
|
|
147
|
+
|
|
148
|
+
if (next) {
|
|
149
|
+
const delay = Math.max(0, Number(this.delay) || 0);
|
|
150
|
+
if (delay === 0) {
|
|
151
|
+
this.#applyBusy();
|
|
152
|
+
} else {
|
|
153
|
+
this.#delayTimer = setTimeout(() => {
|
|
154
|
+
this.#delayTimer = null;
|
|
155
|
+
// Only paint if we're still active — caller may have cleared
|
|
156
|
+
// [active] during the grace window.
|
|
157
|
+
if (this.active) this.#applyBusy();
|
|
158
|
+
}, delay);
|
|
159
|
+
}
|
|
160
|
+
} else if (this.#activeApplied) {
|
|
161
|
+
this.#releaseBusy();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#applyBusy() {
|
|
166
|
+
const parent = this.#parent || this.parentElement;
|
|
167
|
+
if (parent && !parent.hasAttribute('aria-busy')) {
|
|
168
|
+
parent.setAttribute('aria-busy', 'true');
|
|
169
|
+
}
|
|
170
|
+
this.#activeApplied = true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#releaseBusy() {
|
|
174
|
+
const parent = this.#parent || this.parentElement;
|
|
175
|
+
// Only remove aria-busy if WE set it (heuristic: matches "true").
|
|
176
|
+
// If the consumer authored their own aria-busy="true" before we
|
|
177
|
+
// mounted, we leave it; if they set "false" we leave it. Same
|
|
178
|
+
// shape as drawer-ui releasing dialog state.
|
|
179
|
+
if (parent && parent.getAttribute('aria-busy') === 'true') {
|
|
180
|
+
parent.removeAttribute('aria-busy');
|
|
181
|
+
}
|
|
182
|
+
this.#activeApplied = false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#clearTimer() {
|
|
186
|
+
if (this.#delayTimer !== null) {
|
|
187
|
+
clearTimeout(this.#delayTimer);
|
|
188
|
+
this.#delayTimer = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#stampDefaultSpinner() {
|
|
193
|
+
// Imperative stamp — light-DOM child, marked with a data attribute
|
|
194
|
+
// so render()'s "did the consumer slot something?" check stays
|
|
195
|
+
// unambiguous on subsequent renders.
|
|
196
|
+
const sp = document.createElement('spinner-ui');
|
|
197
|
+
sp.setAttribute('size', 'lg');
|
|
198
|
+
sp.setAttribute('tone', 'subtle');
|
|
199
|
+
sp.setAttribute('label', this.label || 'Loading');
|
|
200
|
+
sp.setAttribute('data-loading-overlay-auto', '');
|
|
201
|
+
this.appendChild(sp);
|
|
202
|
+
}
|
|
203
|
+
}
|