@fusion.dev/helix-headless 0.142.1 → 0.144.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +25381 -1202
- package/dist/accordion/item/accordion-item.d.ts +38 -2
- package/dist/accordion/item/accordion-item.js +38 -6
- package/dist/accordion/root/accordion-root.d.ts +23 -1
- package/dist/accordion/root/accordion-root.js +39 -3
- package/dist/announcement/root/announcement-root.d.ts +11 -7
- package/dist/announcement/root/announcement-root.js +1 -1
- package/dist/app-header/root/app-header-root.d.ts +21 -0
- package/dist/app-header/root/app-header-root.js +31 -5
- package/dist/area-chart/root/area-chart-root.d.ts +12 -4
- package/dist/area-chart/root/area-chart-root.js +39 -11
- package/dist/avatar/group/avatar-group.d.ts +24 -3
- package/dist/avatar/group/avatar-group.js +34 -2
- package/dist/avatar/root/avatar-root.d.ts +17 -1
- package/dist/avatar/root/avatar-root.js +31 -10
- package/dist/badge/root/badge-root.d.ts +12 -0
- package/dist/badge/root/badge-root.js +1 -1
- package/dist/bar-chart/root/bar-chart-root.d.ts +30 -5
- package/dist/bar-chart/root/bar-chart-root.js +58 -21
- package/dist/blockquote/root/blockquote-root.d.ts +12 -7
- package/dist/blockquote/root/blockquote-root.js +6 -4
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -3
- package/dist/breadcrumbs/root/breadcrumbs-root.js +9 -1
- package/dist/button/root/button-root.d.ts +84 -1
- package/dist/button/root/button-root.js +81 -3
- package/dist/callout/root/callout-root.d.ts +32 -6
- package/dist/callout/root/callout-root.js +26 -7
- package/dist/canvas/root/canvas-root.d.ts +1 -0
- package/dist/canvas/root/canvas-root.js +10 -1
- package/dist/card/root/card-root.d.ts +30 -1
- package/dist/card/root/card-root.js +38 -5
- package/dist/carousel/root/carousel-root.d.ts +47 -5
- package/dist/carousel/root/carousel-root.js +53 -21
- package/dist/chat/composer/chat-composer.d.ts +13 -11
- package/dist/chat/composer/chat-composer.js +6 -12
- package/dist/chat/message/chat-message.js +8 -1
- package/dist/chat/root/chat-root.d.ts +1 -0
- package/dist/chat/root/chat-root.js +2 -1
- package/dist/checkbox/root/checkbox-root.d.ts +7 -0
- package/dist/checkbox/root/checkbox-root.js +9 -3
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +116 -33
- package/dist/checkbox-group/root/checkbox-group-root.js +141 -44
- package/dist/code/root/code-root.d.ts +1 -1
- package/dist/code-block/lines.d.ts +13 -0
- package/dist/code-block/lines.js +2 -0
- package/dist/code-block/root/code-block-root.d.ts +49 -0
- package/dist/code-block/root/code-block-root.js +44 -3
- package/dist/color-input/root/color-input-root.d.ts +60 -2
- package/dist/color-input/root/color-input-root.js +55 -7
- package/dist/color-picker/channels.d.ts +26 -0
- package/dist/color-picker/channels.js +84 -0
- package/dist/color-picker/root/color-picker-root.d.ts +118 -12
- package/dist/color-picker/root/color-picker-root.js +183 -13
- package/dist/combobox/root/combobox-root.d.ts +126 -3
- package/dist/combobox/root/combobox-root.js +224 -21
- package/dist/command/root/command-root.d.ts +47 -10
- package/dist/command/root/command-root.js +45 -4
- package/dist/copy-button/from.d.ts +22 -0
- package/dist/copy-button/from.js +28 -0
- package/dist/copy-button/root/copy-button-root.d.ts +48 -7
- package/dist/copy-button/root/copy-button-root.js +54 -8
- package/dist/data-table/root/data-table-root.d.ts +164 -10
- package/dist/data-table/root/data-table-root.js +488 -57
- package/dist/date-input/root/date-input-root.d.ts +123 -9
- package/dist/date-input/root/date-input-root.js +147 -45
- package/dist/date-picker/root/date-picker-root.d.ts +229 -18
- package/dist/date-picker/root/date-picker-root.js +342 -46
- package/dist/dialog/description.d.ts +9 -0
- package/dist/dialog/description.js +14 -0
- package/dist/dialog/root/dialog-root.d.ts +101 -10
- package/dist/dialog/root/dialog-root.js +83 -4
- package/dist/donut-chart/root/donut-chart-root.d.ts +61 -16
- package/dist/donut-chart/root/donut-chart-root.js +48 -13
- package/dist/drawer/root/drawer-root.d.ts +93 -7
- package/dist/drawer/root/drawer-root.js +126 -5
- package/dist/drawer/swipe-start.d.ts +9 -0
- package/dist/drawer/swipe-start.js +12 -0
- package/dist/drilldown/item/drilldown-item.d.ts +16 -13
- package/dist/drilldown/item/drilldown-item.js +6 -2
- package/dist/drilldown/root/drilldown-root.d.ts +21 -5
- package/dist/drilldown/root/drilldown-root.js +26 -10
- package/dist/element-events.generated.js +42 -13
- package/dist/empty/root/empty-root.d.ts +1 -1
- package/dist/file-input/root/file-input-root.d.ts +65 -1
- package/dist/file-input/root/file-input-root.js +76 -12
- package/dist/file-input/screen.d.ts +35 -0
- package/dist/file-input/screen.js +49 -0
- package/dist/filter-input/menu/filter-menu.d.ts +9 -0
- package/dist/filter-input/menu/filter-menu.js +8 -0
- package/dist/filter-input/root/filter-input-root.d.ts +43 -6
- package/dist/filter-input/root/filter-input-root.js +89 -10
- package/dist/filter-input/suggest.d.ts +17 -0
- package/dist/filter-input/suggest.js +23 -0
- package/dist/format-date/options.d.ts +33 -0
- package/dist/format-date/options.js +42 -0
- package/dist/format-date/root/format-date-root.d.ts +62 -3
- package/dist/format-date/root/format-date-root.js +78 -37
- package/dist/graph/root/graph-root.d.ts +24 -3
- package/dist/graph/root/graph-root.js +66 -12
- package/dist/heatmap/root/buckets.d.ts +2 -1
- package/dist/heatmap/root/buckets.js +5 -1
- package/dist/heatmap/root/heatmap-root.d.ts +47 -9
- package/dist/heatmap/root/heatmap-root.js +46 -18
- package/dist/icon/index.d.ts +2 -0
- package/dist/icon/index.js +2 -0
- package/dist/icon/registry.d.ts +37 -0
- package/dist/icon/registry.js +83 -0
- package/dist/icon/root/icon-root.d.ts +53 -6
- package/dist/icon/root/icon-root.js +72 -4
- package/dist/icon/root/index.d.ts +2 -0
- package/dist/icon/root/index.js +2 -0
- package/dist/icon/set.d.ts +1 -0
- package/dist/icon/set.js +3 -0
- package/dist/icon-button/root/icon-button-root.d.ts +1 -1
- package/dist/image/root/image-root.d.ts +23 -2
- package/dist/image/root/image-root.js +23 -4
- package/dist/input/root/input-root.d.ts +62 -9
- package/dist/input/root/input-root.js +61 -5
- package/dist/kbd/root/kbd-root.d.ts +26 -11
- package/dist/kbd/root/kbd-root.js +35 -38
- package/dist/kbd/shortcut.d.ts +18 -0
- package/dist/kbd/shortcut.js +35 -0
- package/dist/legend/hidden-series.d.ts +8 -0
- package/dist/legend/hidden-series.js +13 -0
- package/dist/legend/root/legend-root.d.ts +63 -0
- package/dist/legend/root/legend-root.js +61 -7
- package/dist/lightbox/item/lightbox-item.d.ts +17 -1
- package/dist/lightbox/item/lightbox-item.js +10 -1
- package/dist/lightbox/root/lightbox-root.d.ts +77 -6
- package/dist/lightbox/root/lightbox-root.js +116 -5
- package/dist/line-chart/root/line-chart-root.d.ts +29 -4
- package/dist/line-chart/root/line-chart-root.js +58 -17
- package/dist/list/item/list-item.d.ts +11 -5
- package/dist/list/item/list-item.js +5 -6
- package/dist/listbox/range.d.ts +17 -0
- package/dist/listbox/range.js +5 -0
- package/dist/listbox/root/listbox-root.d.ts +184 -4
- package/dist/listbox/root/listbox-root.js +229 -23
- package/dist/loader/busy.d.ts +17 -0
- package/dist/loader/busy.js +24 -0
- package/dist/loader/root/loader-root.d.ts +37 -0
- package/dist/loader/root/loader-root.js +52 -3
- package/dist/map/root/map-root.d.ts +19 -4
- package/dist/map/root/map-root.js +36 -7
- package/dist/megamenu/root/megamenu-root.d.ts +10 -1
- package/dist/megamenu/root/megamenu-root.js +10 -1
- package/dist/menu/item/menu-item.d.ts +41 -12
- package/dist/menu/item/menu-item.js +46 -3
- package/dist/menu/root/menu-root.d.ts +32 -2
- package/dist/menu/root/menu-root.js +48 -8
- package/dist/meter/root/meter-root.d.ts +13 -0
- package/dist/meter/root/meter-root.js +24 -5
- package/dist/navigation/root/navigation-root.d.ts +10 -2
- package/dist/navigation/root/navigation-root.js +13 -2
- package/dist/notifications/feed-keys.d.ts +10 -0
- package/dist/notifications/feed-keys.js +14 -0
- package/dist/notifications/group/notification-group.d.ts +1 -0
- package/dist/notifications/notification/notification.d.ts +29 -0
- package/dist/notifications/notification/notification.js +51 -3
- package/dist/notifications/root/notifications-root.d.ts +49 -0
- package/dist/notifications/root/notifications-root.js +76 -2
- package/dist/number-input/root/number-input-root.d.ts +109 -7
- package/dist/number-input/root/number-input-root.js +109 -21
- package/dist/object-inspector/root/object-inspector-root.d.ts +49 -5
- package/dist/object-inspector/root/object-inspector-root.js +92 -12
- package/dist/option/root/option-root.d.ts +58 -6
- package/dist/option/root/option-root.js +35 -5
- package/dist/outline/root/outline-root.d.ts +13 -2
- package/dist/outline/root/outline-root.js +4 -2
- package/dist/pagination/root/pagination-root.d.ts +19 -1
- package/dist/pagination/root/pagination-root.js +49 -9
- package/dist/pagination/size/pagination-size.d.ts +13 -3
- package/dist/pagination/size/pagination-size.js +7 -0
- package/dist/pin-input/format.d.ts +7 -0
- package/dist/pin-input/format.js +16 -0
- package/dist/pin-input/root/pin-input-root.d.ts +37 -0
- package/dist/pin-input/root/pin-input-root.js +48 -7
- package/dist/popover/root/popover-root.d.ts +78 -3
- package/dist/popover/root/popover-root.js +74 -5
- package/dist/progress/root/progress-root.d.ts +23 -1
- package/dist/progress/root/progress-root.js +40 -10
- package/dist/qr-code/export.d.ts +31 -0
- package/dist/qr-code/export.js +10 -0
- package/dist/qr-code/root/qr-code-root.d.ts +45 -0
- package/dist/qr-code/root/qr-code-root.js +78 -3
- package/dist/radio/group/radio-group.d.ts +64 -0
- package/dist/radio/group/radio-group.js +66 -12
- package/dist/radio/root/radio-root.d.ts +8 -5
- package/dist/radio/root/radio-root.js +1 -2
- package/dist/rating/root/rating-root.d.ts +49 -1
- package/dist/rating/root/rating-root.js +69 -10
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
- package/dist/scatter-chart/root/scatter-chart-root.js +40 -14
- package/dist/select/root/select-root.d.ts +93 -3
- package/dist/select/root/select-root.js +111 -13
- package/dist/skeleton/root/skeleton-root.d.ts +39 -3
- package/dist/skeleton/root/skeleton-root.js +33 -4
- package/dist/skip-link/root/skip-link-root.d.ts +10 -0
- package/dist/skip-link/root/skip-link-root.js +15 -1
- package/dist/slider/root/slider-root.d.ts +59 -2
- package/dist/slider/root/slider-root.js +82 -8
- package/dist/sparkline/root/sparkline-root.d.ts +9 -10
- package/dist/speed-dial/item/speed-dial-item.d.ts +11 -5
- package/dist/speed-dial/item/speed-dial-item.js +7 -2
- package/dist/speed-dial/root/speed-dial-root.d.ts +15 -4
- package/dist/speed-dial/root/speed-dial-root.js +19 -5
- package/dist/splitter/root/splitter-root.d.ts +28 -6
- package/dist/splitter/root/splitter-root.js +35 -1
- package/dist/stat/root/stat-root.d.ts +9 -1
- package/dist/stat/root/stat-root.js +15 -2
- package/dist/steps/root/steps-root.js +1 -1
- package/dist/steps/step/step.d.ts +1 -0
- package/dist/steps/step/step.js +1 -1
- package/dist/switch/root/switch-root.d.ts +14 -2
- package/dist/switch/root/switch-root.js +7 -1
- package/dist/table/cell/table-cell.d.ts +24 -4
- package/dist/table/cell/table-cell.js +21 -1
- package/dist/table/column/table-column.d.ts +32 -6
- package/dist/table/column/table-column.js +36 -6
- package/dist/table/root/table-root.d.ts +94 -3
- package/dist/table/root/table-root.js +128 -5
- package/dist/table/row/table-row.d.ts +7 -0
- package/dist/table/row/table-row.js +9 -1
- package/dist/tabs/root/tabs-root.d.ts +6 -1
- package/dist/tabs/root/tabs-root.js +6 -2
- package/dist/tag/group/tag-group.d.ts +107 -0
- package/dist/tag/group/tag-group.js +157 -0
- package/dist/tag/index.d.ts +1 -0
- package/dist/tag/index.js +1 -0
- package/dist/tag/root/tag-root.d.ts +83 -1
- package/dist/tag/root/tag-root.js +87 -17
- package/dist/tag/selection.d.ts +10 -0
- package/dist/tag/selection.js +12 -0
- package/dist/textarea/grow.d.ts +22 -0
- package/dist/textarea/grow.js +20 -0
- package/dist/textarea/root/textarea-root.d.ts +13 -0
- package/dist/textarea/root/textarea-root.js +10 -2
- package/dist/time-input/root/time-input-root.d.ts +71 -6
- package/dist/time-input/root/time-input-root.js +110 -44
- package/dist/time-picker/root/time-picker-root.d.ts +155 -26
- package/dist/time-picker/root/time-picker-root.js +192 -63
- package/dist/toast/hotkey.d.ts +22 -0
- package/dist/toast/hotkey.js +23 -0
- package/dist/toast/item/toast-item.d.ts +16 -1
- package/dist/toast/item/toast-item.js +41 -13
- package/dist/toast/root/toast-root.d.ts +105 -8
- package/dist/toast/root/toast-root.js +209 -4
- package/dist/toggle/group/toggle-group.d.ts +95 -16
- package/dist/toggle/group/toggle-group.js +99 -13
- package/dist/toggle/root/toggle-root.d.ts +26 -11
- package/dist/toggle/root/toggle-root.js +14 -6
- package/dist/toolbar/root/toolbar-root.d.ts +55 -15
- package/dist/toolbar/root/toolbar-root.js +99 -16
- package/dist/tooltip/focus-target.d.ts +6 -0
- package/dist/tooltip/focus-target.js +9 -0
- package/dist/tooltip/root/tooltip-root.d.ts +73 -15
- package/dist/tooltip/root/tooltip-root.js +96 -7
- package/dist/tour/naming.d.ts +30 -0
- package/dist/tour/naming.js +10 -0
- package/dist/tour/root/tour-root.d.ts +67 -3
- package/dist/tour/root/tour-root.js +105 -17
- package/dist/tour/stop/tour-stop.d.ts +65 -3
- package/dist/tour/stop/tour-stop.js +61 -11
- package/dist/tour/wait.d.ts +11 -0
- package/dist/tour/wait.js +19 -0
- package/dist/tree/item/tree-item.d.ts +21 -5
- package/dist/tree/item/tree-item.js +14 -4
- package/dist/tree/root/tree-root.d.ts +29 -5
- package/dist/tree/root/tree-root.js +64 -8
- package/dist/trend/root/trend-root.d.ts +36 -1
- package/dist/trend/root/trend-root.js +41 -15
- package/dist/video/root/keys.d.ts +5 -0
- package/dist/video/root/keys.js +13 -1
- package/dist/video/root/video-root.d.ts +19 -0
- package/dist/video/root/video-root.js +48 -4
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +20 -2
- package/dist/video-playlist/menu/video-playlist-menu.js +27 -5
- package/dist/video-playlist/root/video-playlist-root.js +6 -3
- package/dist/virtual-list/root/virtual-list-root.d.ts +52 -4
- package/dist/virtual-list/root/virtual-list-root.js +103 -21
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +9140 -1149
- package/types/svelte.d.ts +1 -0
- package/types/vue.d.ts +2089 -267
|
@@ -9,11 +9,19 @@ import { html, nothing, svg } from 'lit';
|
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import '../../tooltip/index.js';
|
|
11
11
|
const REACH = 24;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
const COEFFICIENTS = new Map();
|
|
13
|
+
const coefficientIn = (locale) => {
|
|
14
|
+
let found = COEFFICIENTS.get(locale);
|
|
15
|
+
if (!found) {
|
|
16
|
+
found = new Intl.NumberFormat(locale || undefined, {
|
|
17
|
+
maximumFractionDigits: 2,
|
|
18
|
+
minimumFractionDigits: 2,
|
|
19
|
+
signDisplay: 'exceptZero',
|
|
20
|
+
});
|
|
21
|
+
COEFFICIENTS.set(locale, found);
|
|
22
|
+
}
|
|
23
|
+
return found;
|
|
24
|
+
};
|
|
17
25
|
const nice = ([low, high]) => {
|
|
18
26
|
if (low === high) {
|
|
19
27
|
return [low - 1, high + 1];
|
|
@@ -108,14 +116,17 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
|
|
|
108
116
|
const y = row.cells[column]?.value;
|
|
109
117
|
return x === undefined || y === undefined ? [] : [[x, y, index]];
|
|
110
118
|
}));
|
|
111
|
-
const
|
|
119
|
+
const shown = ups.map((column) => this.showsSeries(name(column)));
|
|
120
|
+
const all = pairs.filter((_, depth) => shown[depth]).flat();
|
|
112
121
|
const x = nice(extent(all.length > 0 ? all.map(([one]) => one) : [0]));
|
|
113
122
|
const y = nice(extent(all.length > 0 ? all.map(([, one]) => one) : [0]));
|
|
114
123
|
const along = linear(x, [0, PLOT_WIDTH]);
|
|
115
124
|
const up = linear(y, [PLOT_HEIGHT, 0]);
|
|
116
125
|
return {
|
|
117
126
|
pairs: pairs.map((series) => series.map(([one, two]) => [one, two])),
|
|
118
|
-
points: pairs.flatMap((series, depth) =>
|
|
127
|
+
points: pairs.flatMap((series, depth) => shown[depth]
|
|
128
|
+
? series.map(([one, two, row]) => ({ depth, row, x: along(one), y: up(two) }))
|
|
129
|
+
: []),
|
|
119
130
|
x,
|
|
120
131
|
xName: name(across),
|
|
121
132
|
y,
|
|
@@ -130,7 +141,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
|
|
|
130
141
|
<div id=${CHART_PLOT} part="plot"></div>
|
|
131
142
|
</div>`;
|
|
132
143
|
}
|
|
133
|
-
const format = formatNumbers(
|
|
144
|
+
const format = formatNumbers(this.locale);
|
|
134
145
|
const along = linear(field.x, [0, PLOT_WIDTH]);
|
|
135
146
|
const up = linear(field.y, [PLOT_HEIGHT, 0]);
|
|
136
147
|
return html `<div dir="ltr" part="plane">
|
|
@@ -148,8 +159,17 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
|
|
|
148
159
|
}
|
|
149
160
|
renderPlot(field, along, up) {
|
|
150
161
|
const at = this.pointer.at;
|
|
151
|
-
return html `<div
|
|
162
|
+
return html `<div
|
|
163
|
+
aria-describedby=${this.named ? 'helix-chart-summary' : nothing}
|
|
164
|
+
aria-label=${this.named || nothing}
|
|
165
|
+
aria-labelledby=${this.named ? nothing : 'helix-chart-summary'}
|
|
166
|
+
id=${CHART_PLOT}
|
|
167
|
+
part="plot"
|
|
168
|
+
role="application"
|
|
169
|
+
tabindex="0"
|
|
170
|
+
>
|
|
152
171
|
<svg part="svg"
|
|
172
|
+
aria-hidden="true"
|
|
153
173
|
preserveAspectRatio="none"
|
|
154
174
|
viewBox="0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}"
|
|
155
175
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -164,6 +184,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
|
|
|
164
184
|
${field.points.map((point) => {
|
|
165
185
|
const index = seriesIndex(point.depth);
|
|
166
186
|
return html `<span
|
|
187
|
+
aria-hidden="true"
|
|
167
188
|
data-series=${index}
|
|
168
189
|
?data-active=${point.row === at}
|
|
169
190
|
part="point${point.row === at ? ' point-active' : ''}"
|
|
@@ -177,17 +198,22 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
|
|
|
177
198
|
const clauses = field.yNames.map((name, depth) => {
|
|
178
199
|
const pairs = field.pairs[depth] ?? [];
|
|
179
200
|
const r = correlation(pairs);
|
|
180
|
-
const strength = r === undefined ? '' : `, ${this.correlationLabel} ${
|
|
201
|
+
const strength = r === undefined ? '' : `, ${this.correlationLabel} ${coefficientIn(this.locale).format(r)}`;
|
|
181
202
|
return `${name} / ${field.xName}: ${pairs.length} ${this.pointsLabel}${strength}`;
|
|
182
203
|
});
|
|
183
204
|
const named = this.named ? `${this.named}. ` : '';
|
|
184
|
-
return html `<p part="summary">${named}${clauses.join('; ')}.</p>`;
|
|
205
|
+
return html `<p id="helix-chart-summary" part="summary">${named}${clauses.join('; ')}.</p>`;
|
|
185
206
|
}
|
|
186
207
|
renderTooltip() {
|
|
187
208
|
const at = this.pointer.at;
|
|
188
|
-
const
|
|
209
|
+
const column = this.pointer.column;
|
|
210
|
+
const depth = column === undefined ? -1 : this.plottedColumns.indexOf(column) - 1;
|
|
211
|
+
const point = this.nearest?.row === at && !this.pointer.keyed
|
|
212
|
+
? this.nearest
|
|
213
|
+
: (this.drawn.find((one) => one.row === at && one.depth === depth) ??
|
|
214
|
+
this.drawn.find((one) => one.row === at));
|
|
189
215
|
if (at < 0 || !point) {
|
|
190
|
-
return drawTooltip(this.sourceRows, -1, this.tag('tooltip'));
|
|
216
|
+
return drawTooltip(this.sourceRows, -1, this.tag('tooltip'), undefined, undefined, this.pointer);
|
|
191
217
|
}
|
|
192
218
|
return drawTooltip(this.sourceRows, at, this.tag('tooltip'), {
|
|
193
219
|
dir: isRtl(this) ? 'rtl' : 'ltr',
|
|
@@ -195,7 +221,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
|
|
|
195
221
|
placement: 'top',
|
|
196
222
|
x: `${point.x}%`,
|
|
197
223
|
y: `${point.y}%`,
|
|
198
|
-
}, (cell) => this.seriesOf(cell));
|
|
224
|
+
}, (cell) => this.seriesOf(cell), this.pointer);
|
|
199
225
|
}
|
|
200
226
|
};
|
|
201
227
|
__decorate([
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AriaForwardController, HelixFormElement, PopoverController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
import { type HelixHeadlessOption } from '../../option/root/option-root.js';
|
|
3
4
|
import '../../listbox/index.js';
|
|
4
5
|
declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
|
|
5
6
|
/**
|
|
@@ -17,6 +18,17 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
|
|
|
17
18
|
* `Select` owns the value, passes it down to the listbox, and takes changes
|
|
18
19
|
* back, so the value lives in one place.
|
|
19
20
|
*
|
|
21
|
+
* Set `valueContent` to draw the trigger's content from the chosen options,
|
|
22
|
+
* and several chosen options are written as a list in the reader's language.
|
|
23
|
+
*
|
|
24
|
+
* In the open list the arrow keys, Home, End, PageUp, PageDown and typed
|
|
25
|
+
* letters move the highlight, and Enter, Space or a click chooses it and
|
|
26
|
+
* closes the list. Tab closes the list and moves on. On the closed trigger,
|
|
27
|
+
* ArrowDown, Enter and Space open the list on the chosen option, ArrowUp opens
|
|
28
|
+
* it on the chosen or last option, Home and End open it on the first or last,
|
|
29
|
+
* and typed letters choose the option whose words start with them, as a native
|
|
30
|
+
* `<select>` does.
|
|
31
|
+
*
|
|
20
32
|
* @since 0.1.0
|
|
21
33
|
* @status stable
|
|
22
34
|
* @category forms
|
|
@@ -25,6 +37,7 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
|
|
|
25
37
|
*
|
|
26
38
|
* @slot - The options, and any groups of them.
|
|
27
39
|
* @slot end - Content after the chosen value, such as a count, a unit, or a clear button.
|
|
40
|
+
* @slot feedback - The message that says what is wrong, for content richer than a string. It shows while there is a message.
|
|
28
41
|
* @slot hint - The hint, for content richer than plain text.
|
|
29
42
|
* @slot indicator - The icon on the trigger that shows it opens, usually a caret.
|
|
30
43
|
* @slot label - The label, for content richer than plain text.
|
|
@@ -34,8 +47,9 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
|
|
|
34
47
|
* @event {HelixBeforeChangeEvent} helix-before-change - Relayed from the listbox when the value is about to change. Cancelable: canceling it stops the change and leaves the decision to you.
|
|
35
48
|
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before the list closes. Cancelable: canceling it keeps the list open and leaves the decision to you.
|
|
36
49
|
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before the list opens. Cancelable: canceling it keeps the list closed and leaves the decision to you.
|
|
50
|
+
* @event {HelixFilterEvent} helix-filter - Fired as the user types in the filter field of a `filterable` select, with the text the list is filtered by. Cancelable: cancel it to leave the list unfiltered and filter the options yourself.
|
|
37
51
|
* @event {Event} input - Fired after the value changes.
|
|
38
|
-
* @event {
|
|
52
|
+
* @event {ToggleEvent} toggle - Fired after the list opens or closes, with `newState` and `oldState`.
|
|
39
53
|
*
|
|
40
54
|
* @csspart feedback - The message that says what is wrong with the value.
|
|
41
55
|
* @csspart hint - Help under the trigger.
|
|
@@ -93,6 +107,17 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
93
107
|
* Shown on the trigger while nothing is chosen.
|
|
94
108
|
*/
|
|
95
109
|
placeholder: string;
|
|
110
|
+
/**
|
|
111
|
+
* What the trigger shows for the chosen options: a function given them, in
|
|
112
|
+
* the order they appear, that returns text, an element or a Lit template.
|
|
113
|
+
*
|
|
114
|
+
* For a trigger that shows more than the words, an avatar beside a name or a
|
|
115
|
+
* count of a long set, as React Aria's `SelectValue` takes children. The
|
|
116
|
+
* trigger is named by what this draws, so keep the words in it. Nothing
|
|
117
|
+
* chosen still shows `placeholder`. A property only, there being no way to
|
|
118
|
+
* write a function in markup.
|
|
119
|
+
*/
|
|
120
|
+
valueContent?: ((chosen: readonly HelixHeadlessOption[]) => unknown) | undefined;
|
|
96
121
|
/**
|
|
97
122
|
* Which option is chosen, as an index, or `-1` where none is.
|
|
98
123
|
*
|
|
@@ -158,10 +183,73 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
158
183
|
* the commit creates the text node it needs.
|
|
159
184
|
*/
|
|
160
185
|
private get display();
|
|
186
|
+
/** The chosen options, in the order they appear. None on a server or while hydrating. */
|
|
187
|
+
private get chosenOptions();
|
|
188
|
+
/**
|
|
189
|
+
* What the trigger shows, behind a seam: what `value-content` draws for the
|
|
190
|
+
* chosen options, or their words, or the placeholder.
|
|
191
|
+
*/
|
|
192
|
+
protected renderValue(): unknown;
|
|
193
|
+
/** Where the highlight goes when the list opens, set by the key that opened it. */
|
|
194
|
+
private opening;
|
|
195
|
+
/**
|
|
196
|
+
* The values of the options seen on the last update, so an option that
|
|
197
|
+
* leaves can be told apart from one that has not arrived yet.
|
|
198
|
+
*/
|
|
199
|
+
private seen;
|
|
200
|
+
/**
|
|
201
|
+
* Hears an option's words change, which no slot reports, and the host's
|
|
202
|
+
* `aria-labelledby` change, which `triggerNames` reads.
|
|
203
|
+
*
|
|
204
|
+
* The trigger shows the chosen option's words, read at render time. A slot
|
|
205
|
+
* reports an option arriving or leaving and says nothing when the text of
|
|
206
|
+
* one already there changes, so a relabeled option left the old words on the
|
|
207
|
+
* trigger. `label` and `value` are attributes that change the words too.
|
|
208
|
+
*
|
|
209
|
+
* Made on connection, there being no `MutationObserver` on a server.
|
|
210
|
+
*/
|
|
211
|
+
private watchers;
|
|
212
|
+
/** A link option, once followed, closes the panel as a choice does. */
|
|
213
|
+
private readonly handleLinkFollowed;
|
|
214
|
+
connectedCallback(): void;
|
|
215
|
+
disconnectedCallback(): void;
|
|
216
|
+
/** Opens the list with the highlight where the opening key says. */
|
|
217
|
+
private openAt;
|
|
161
218
|
/** Closes the list and puts focus back where it came from. */
|
|
162
219
|
private close;
|
|
163
220
|
private readonly handleChange;
|
|
221
|
+
/**
|
|
222
|
+
* The keys of the closed trigger.
|
|
223
|
+
*
|
|
224
|
+
* Letters choose without opening, the way a native `<select>` and React
|
|
225
|
+
* Aria's Select do, so a reader who knows the answer types it. A Space in the
|
|
226
|
+
* middle of those letters is one of them. With `multiple`, a letter opens the
|
|
227
|
+
* list on the match instead, because adding to a set is a choice the reader
|
|
228
|
+
* makes in the list.
|
|
229
|
+
*/
|
|
164
230
|
private readonly handleTriggerKeydown;
|
|
231
|
+
/**
|
|
232
|
+
* Tab closes the list and lets focus move on, as a native `<select>` does.
|
|
233
|
+
*
|
|
234
|
+
* Measured before this: Tab moved focus to the next control and left the
|
|
235
|
+
* panel open over the page, because the panel closes on a press outside or
|
|
236
|
+
* on Escape and nothing closed it when focus left.
|
|
237
|
+
*/
|
|
238
|
+
private readonly handleListKeydown;
|
|
239
|
+
/**
|
|
240
|
+
* Takes `<helix-option selected>` as the default value, and an option that
|
|
241
|
+
* has left out of the value.
|
|
242
|
+
*
|
|
243
|
+
* The first is what a native `<select>` does with `<option selected>`, and
|
|
244
|
+
* what `Option.defaultSelected` says: a form reset returns to it. A
|
|
245
|
+
* `default-value` on the select wins.
|
|
246
|
+
*
|
|
247
|
+
* The second keeps the trigger and the form in agreement. Measured before
|
|
248
|
+
* this: removing the chosen option made the trigger say "Choose" while the
|
|
249
|
+
* form still submitted the removed value. Only an option seen before is
|
|
250
|
+
* dropped, so a value set ahead of options that load later is kept.
|
|
251
|
+
*/
|
|
252
|
+
private reconcileOptions;
|
|
165
253
|
/** Reports the answer and the verdict on it. */
|
|
166
254
|
private sync;
|
|
167
255
|
protected willUpdate(changed: PropertyValues): void;
|
|
@@ -198,8 +286,10 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
198
286
|
* native `<select>` says both, and this names the trigger by both, the label
|
|
199
287
|
* and then the value, in that order.
|
|
200
288
|
*
|
|
201
|
-
* A consumer's own `aria-labelledby` on the host wins,
|
|
202
|
-
*
|
|
289
|
+
* A consumer's own `aria-labelledby` on the host wins, and so does a
|
|
290
|
+
* `<label>` that names the host where there is no `label`. Both are carried
|
|
291
|
+
* to the trigger by `AriaForwardController`, which reads the value after
|
|
292
|
+
* them, so nothing is written here to fight it.
|
|
203
293
|
*/
|
|
204
294
|
private get triggerNames();
|
|
205
295
|
protected render(): TemplateResult;
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
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
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, customElement, HelixFormElement, hostAria, MixinControllerOpen, MixinControllerValue, MixinField, PopoverController, reportValues, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, afterLinkFollowed, customElement, HelixFormElement, hostAria, MixinControllerOpen, MixinControllerValue, MixinField, PopoverController, reportValues, typesCharacter, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretDown } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
@@ -16,6 +16,10 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
16
16
|
constructor() {
|
|
17
17
|
super(...arguments);
|
|
18
18
|
this.aria = new AriaForwardController(this, {
|
|
19
|
+
labelledByAfter: () => {
|
|
20
|
+
const value = this.partNamed('value');
|
|
21
|
+
return value ? [value] : [];
|
|
22
|
+
},
|
|
19
23
|
target: () => this.partNamed('trigger'),
|
|
20
24
|
});
|
|
21
25
|
this.emptyText = 'No matches';
|
|
@@ -36,6 +40,12 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
36
40
|
placement: 'bottom-start',
|
|
37
41
|
surface: () => this.partNamed('surface'),
|
|
38
42
|
});
|
|
43
|
+
this.opening = 'selected';
|
|
44
|
+
this.seen = new Set();
|
|
45
|
+
this.watchers = [];
|
|
46
|
+
this.handleLinkFollowed = (event) => {
|
|
47
|
+
afterLinkFollowed(event, () => this.requestOpen(false));
|
|
48
|
+
};
|
|
39
49
|
this.handleChange = (event) => {
|
|
40
50
|
const next = event.detail.value;
|
|
41
51
|
if (!this.requestValue(next)) {
|
|
@@ -48,9 +58,39 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
48
58
|
event.stopPropagation();
|
|
49
59
|
};
|
|
50
60
|
this.handleTriggerKeydown = (event) => {
|
|
51
|
-
|
|
61
|
+
const listbox = this.listbox;
|
|
62
|
+
if (typesCharacter(event) && (event.key !== ' ' || listbox?.typing)) {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
const match = listbox?.typeaheadTo(event.key);
|
|
65
|
+
if (match === undefined) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (this.multiple) {
|
|
69
|
+
this.opening = 'selected';
|
|
70
|
+
this.requestOpen(true);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.requestValue(match);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const opens = {
|
|
78
|
+
' ': 'selected',
|
|
79
|
+
ArrowDown: 'selected',
|
|
80
|
+
ArrowUp: 'last',
|
|
81
|
+
End: 'last',
|
|
82
|
+
Enter: 'selected',
|
|
83
|
+
Home: 'first',
|
|
84
|
+
};
|
|
85
|
+
const where = opens[event.key];
|
|
86
|
+
if (where !== undefined) {
|
|
52
87
|
event.preventDefault();
|
|
53
|
-
this.
|
|
88
|
+
this.openAt(where === 'last' && event.key === 'ArrowUp' && this.value !== '' ? 'selected' : where);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
this.handleListKeydown = (event) => {
|
|
92
|
+
if (event.key === 'Tab' && this.open) {
|
|
93
|
+
this.requestOpen(false);
|
|
54
94
|
}
|
|
55
95
|
};
|
|
56
96
|
}
|
|
@@ -58,10 +98,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
58
98
|
return this.listbox?.selectedIndex ?? -1;
|
|
59
99
|
}
|
|
60
100
|
set selectedIndex(at) {
|
|
61
|
-
|
|
62
|
-
if (listbox) {
|
|
63
|
-
listbox.selectedIndex = at;
|
|
64
|
-
}
|
|
101
|
+
this.value = this.listbox?.options[at]?.value ?? '';
|
|
65
102
|
}
|
|
66
103
|
get type() {
|
|
67
104
|
return this.multiple ? 'select-multiple' : 'select-one';
|
|
@@ -71,14 +108,46 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
71
108
|
}
|
|
72
109
|
get display() {
|
|
73
110
|
const chosen = new Set(this.value === '' ? [] : this.value.split(','));
|
|
74
|
-
const words =
|
|
75
|
-
.filter((option) => chosen.has(option.value))
|
|
76
|
-
.map((option) => option.text);
|
|
111
|
+
const words = this.chosenOptions.map((option) => option.text);
|
|
77
112
|
if (words.length > 0) {
|
|
78
|
-
return
|
|
113
|
+
return new Intl.ListFormat(this.lang || undefined, { style: 'short', type: 'unit' }).format(words);
|
|
79
114
|
}
|
|
80
115
|
return chosen.size > 0 && !this.childrenKnown ? nothing : this.placeholder;
|
|
81
116
|
}
|
|
117
|
+
get chosenOptions() {
|
|
118
|
+
const chosen = new Set(this.value === '' ? [] : this.value.split(','));
|
|
119
|
+
return optionsAmong(this.knownChildren).filter((option) => chosen.has(option.value));
|
|
120
|
+
}
|
|
121
|
+
renderValue() {
|
|
122
|
+
const chosen = this.chosenOptions;
|
|
123
|
+
return this.valueContent && chosen.length > 0 ? this.valueContent(chosen) : this.display;
|
|
124
|
+
}
|
|
125
|
+
connectedCallback() {
|
|
126
|
+
super.connectedCallback();
|
|
127
|
+
this.addEventListener('helix-select', this.handleLinkFollowed);
|
|
128
|
+
const words = new MutationObserver(() => this.requestUpdate());
|
|
129
|
+
words.observe(this, {
|
|
130
|
+
attributeFilter: ['label', 'value'],
|
|
131
|
+
characterData: true,
|
|
132
|
+
childList: true,
|
|
133
|
+
subtree: true,
|
|
134
|
+
});
|
|
135
|
+
const naming = new MutationObserver(() => this.requestUpdate());
|
|
136
|
+
naming.observe(this, { attributeFilter: ['aria-labelledby'] });
|
|
137
|
+
this.watchers = [words, naming];
|
|
138
|
+
}
|
|
139
|
+
disconnectedCallback() {
|
|
140
|
+
super.disconnectedCallback();
|
|
141
|
+
this.removeEventListener('helix-select', this.handleLinkFollowed);
|
|
142
|
+
for (const watcher of this.watchers) {
|
|
143
|
+
watcher.disconnect();
|
|
144
|
+
}
|
|
145
|
+
this.watchers = [];
|
|
146
|
+
}
|
|
147
|
+
openAt(where) {
|
|
148
|
+
this.opening = where;
|
|
149
|
+
this.requestOpen(true);
|
|
150
|
+
}
|
|
82
151
|
close() {
|
|
83
152
|
if (!this.open) {
|
|
84
153
|
return;
|
|
@@ -86,6 +155,24 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
86
155
|
this.requestOpen(false);
|
|
87
156
|
this.partNamed('trigger')?.focus();
|
|
88
157
|
}
|
|
158
|
+
reconcileOptions() {
|
|
159
|
+
const options = optionsAmong(this.knownChildren);
|
|
160
|
+
if (this.defaultValue === '' && !this.hasAttribute('default-value')) {
|
|
161
|
+
const declared = options
|
|
162
|
+
.filter((option) => option.defaultSelected)
|
|
163
|
+
.map((option) => option.value);
|
|
164
|
+
if (declared.length > 0) {
|
|
165
|
+
this.defaultValue = this.multiple ? declared.join(',') : (declared.at(-1) ?? '');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const present = new Set(options.map((option) => option.value));
|
|
169
|
+
const held = this.value === '' ? [] : this.value.split(',');
|
|
170
|
+
const gone = held.filter((value) => this.seen.has(value) && !present.has(value));
|
|
171
|
+
this.seen = present;
|
|
172
|
+
if (gone.length > 0) {
|
|
173
|
+
this.value = held.filter((value) => !gone.includes(value)).join(',');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
89
176
|
sync() {
|
|
90
177
|
if (this.multiple) {
|
|
91
178
|
reportValues(this.internals, this.name, this.value === '' ? [] : this.value.split(','));
|
|
@@ -98,6 +185,9 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
98
185
|
willUpdate(changed) {
|
|
99
186
|
super.willUpdate(changed);
|
|
100
187
|
hostAria(this.internals, { label: this.label });
|
|
188
|
+
if (this.childrenKnown) {
|
|
189
|
+
this.reconcileOptions();
|
|
190
|
+
}
|
|
101
191
|
}
|
|
102
192
|
firstUpdated(changed) {
|
|
103
193
|
super.firstUpdated(changed);
|
|
@@ -109,6 +199,8 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
109
199
|
this.linkHost();
|
|
110
200
|
if (this.open) {
|
|
111
201
|
if (changed.has('open')) {
|
|
202
|
+
this.listbox?.highlight(this.opening);
|
|
203
|
+
this.opening = 'selected';
|
|
112
204
|
this.listbox?.focus();
|
|
113
205
|
}
|
|
114
206
|
}
|
|
@@ -129,7 +221,9 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
129
221
|
.value=${this.value}
|
|
130
222
|
?multiple=${this.multiple}
|
|
131
223
|
?filterable=${this.filterable}
|
|
224
|
+
manual
|
|
132
225
|
aria-label=${this.label || nothing}
|
|
226
|
+
@keydown=${this.handleListKeydown}
|
|
133
227
|
@helix-before-change=${this.handleChange}
|
|
134
228
|
@change=${this.stopInner}
|
|
135
229
|
@input=${this.stopInner}
|
|
@@ -147,7 +241,8 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
147
241
|
</slot>`;
|
|
148
242
|
}
|
|
149
243
|
get triggerNames() {
|
|
150
|
-
|
|
244
|
+
const outside = (this.internals.labels?.length ?? 0) > 0 && !this.labeled;
|
|
245
|
+
if (this.hasAttribute('aria-labelledby') || outside) {
|
|
151
246
|
return nothing;
|
|
152
247
|
}
|
|
153
248
|
return this.labeled ? `${this.labelId} value` : 'value';
|
|
@@ -168,7 +263,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
168
263
|
@keydown=${this.handleTriggerKeydown}
|
|
169
264
|
>
|
|
170
265
|
<slot name="start"></slot>
|
|
171
|
-
<span part="value" id="value">${this.
|
|
266
|
+
<span part="value" id="value">${this.renderValue()}</span>
|
|
172
267
|
${''}
|
|
173
268
|
<slot name="end"></slot>
|
|
174
269
|
${''}
|
|
@@ -199,6 +294,9 @@ __decorate([
|
|
|
199
294
|
__decorate([
|
|
200
295
|
property()
|
|
201
296
|
], HelixHeadlessSelect.prototype, "placeholder", void 0);
|
|
297
|
+
__decorate([
|
|
298
|
+
property({ attribute: false })
|
|
299
|
+
], HelixHeadlessSelect.prototype, "valueContent", void 0);
|
|
202
300
|
HelixHeadlessSelect = __decorate([
|
|
203
301
|
customElement('helix-headless-select')
|
|
204
302
|
], HelixHeadlessSelect);
|
|
@@ -4,23 +4,59 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
4
4
|
* @summary A placeholder shown in place of content that is still loading.
|
|
5
5
|
*
|
|
6
6
|
* @description
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* A skeleton is hidden from screen readers unless it has a `label`. An empty
|
|
8
|
+
* placeholder has nothing to say, and a page of them would be a page of
|
|
9
|
+
* unnamed shapes. Tell a screen reader that a region is loading on the region
|
|
10
|
+
* itself: set `aria-busy="true"` on it while it loads, and put a `Loader` or a
|
|
11
|
+
* status message in it. Set `label` on one skeleton to have it read out by
|
|
12
|
+
* name, such as "Loading profile", when a reader reaches it. The role and the
|
|
13
|
+
* name are set through `ElementInternals`, which writes no attributes.
|
|
10
14
|
*
|
|
11
15
|
* There is no `shape` property. Whether a placeholder is a line, a block, or a
|
|
12
16
|
* circle is presentation, which belongs to the styled tier.
|
|
13
17
|
*
|
|
18
|
+
* **Put the real content inside and set `loading`, and the skeleton takes its
|
|
19
|
+
* size.** While `loading` is set the content is inert, the host is
|
|
20
|
+
* `aria-busy`, and the styled tier hides the content and paints the
|
|
21
|
+
* placeholder over its box, so nothing moves when the content arrives. Take
|
|
22
|
+
* `loading` off and the content is shown as it is. An empty skeleton is a
|
|
23
|
+
* placeholder of its own size, as before.
|
|
24
|
+
*
|
|
14
25
|
* @since 0.1.0
|
|
15
26
|
* @status stable
|
|
16
27
|
* @category feedback
|
|
17
28
|
* @aliases Placeholder, Shimmer, Ghost
|
|
18
29
|
* @documentation https://fusion.dev/helix/components/skeleton
|
|
19
30
|
*
|
|
31
|
+
* @slot - The content the skeleton stands in for, which sizes it. Optional.
|
|
32
|
+
*
|
|
33
|
+
* @csspart content - The box around the content, inert while `loading`.
|
|
20
34
|
* @csspart skeleton - The placeholder shape.
|
|
21
35
|
*/
|
|
22
36
|
export declare class HelixHeadlessSkeleton extends HelixElement {
|
|
37
|
+
/**
|
|
38
|
+
* What is loading, read out as the placeholder's name, such as "Loading
|
|
39
|
+
* profile".
|
|
40
|
+
*
|
|
41
|
+
* Absent hides the placeholder from screen readers.
|
|
42
|
+
*/
|
|
43
|
+
label: string;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the content inside is still loading, which keeps it inert and
|
|
46
|
+
* draws the placeholder over it.
|
|
47
|
+
*
|
|
48
|
+
* Absent shows the content. An empty skeleton is a placeholder whatever
|
|
49
|
+
* this says. Reflected, because the styled tier hides the content on it.
|
|
50
|
+
*/
|
|
51
|
+
loading: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the skeleton holds content, read off its children where they can
|
|
54
|
+
* be known. Not on a server, where an unlabeled skeleton is taken for an
|
|
55
|
+
* empty one until the page has its children.
|
|
56
|
+
*/
|
|
57
|
+
private get wraps();
|
|
23
58
|
protected willUpdate(changed: PropertyValues): void;
|
|
59
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
24
60
|
protected render(): TemplateResult;
|
|
25
61
|
}
|
|
26
62
|
declare global {
|
|
@@ -4,18 +4,47 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
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
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, HelixElement } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
|
+
import { property } from 'lit/decorators.js';
|
|
9
10
|
let HelixHeadlessSkeleton = class HelixHeadlessSkeleton extends HelixElement {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.label = '';
|
|
14
|
+
this.loading = false;
|
|
15
|
+
}
|
|
16
|
+
get wraps() {
|
|
17
|
+
return (this.childrenKnown &&
|
|
18
|
+
[...this.childNodes].some((node) => node.nodeType === 1 || (node.textContent ?? '').trim() !== ''));
|
|
19
|
+
}
|
|
10
20
|
willUpdate(changed) {
|
|
11
21
|
super.willUpdate(changed);
|
|
12
|
-
this.internals.ariaBusy = 'true';
|
|
13
|
-
this.
|
|
22
|
+
this.internals.ariaBusy = this.loading ? 'true' : null;
|
|
23
|
+
if (this.label) {
|
|
24
|
+
hostAria(this.internals, { label: this.label, role: 'progressbar' });
|
|
25
|
+
this.internals.ariaHidden = null;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
hostAria(this.internals, { label: '', role: '' });
|
|
29
|
+
this.internals.ariaHidden = this.wraps ? null : 'true';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
firstUpdated(changed) {
|
|
33
|
+
super.firstUpdated(changed);
|
|
34
|
+
this.finishHydration();
|
|
14
35
|
}
|
|
15
36
|
render() {
|
|
16
|
-
return html `<span part="
|
|
37
|
+
return html `<span part="content" ?inert=${this.loading}
|
|
38
|
+
><slot @slotchange=${() => this.requestUpdate()}></slot></span
|
|
39
|
+
><span part="skeleton"></span>`;
|
|
17
40
|
}
|
|
18
41
|
};
|
|
42
|
+
__decorate([
|
|
43
|
+
property()
|
|
44
|
+
], HelixHeadlessSkeleton.prototype, "label", void 0);
|
|
45
|
+
__decorate([
|
|
46
|
+
property({ reflect: true, type: Boolean })
|
|
47
|
+
], HelixHeadlessSkeleton.prototype, "loading", void 0);
|
|
19
48
|
HelixHeadlessSkeleton = __decorate([
|
|
20
49
|
customElement('helix-headless-skeleton')
|
|
21
50
|
], HelixHeadlessSkeleton);
|
|
@@ -75,6 +75,16 @@ export declare class HelixHeadlessSkipLink extends HelixElement {
|
|
|
75
75
|
* region.
|
|
76
76
|
*/
|
|
77
77
|
private readonly skip;
|
|
78
|
+
/** The `href` the last warning was for, so a page hears once per target. */
|
|
79
|
+
private missed;
|
|
80
|
+
/**
|
|
81
|
+
* Says so when an in-page `href` names nothing.
|
|
82
|
+
*
|
|
83
|
+
* Asked on a press and not on connection: the link is first in the
|
|
84
|
+
* document, so its target is usually parsed or rendered after it, and an
|
|
85
|
+
* early check would warn about a page that is still arriving.
|
|
86
|
+
*/
|
|
87
|
+
private reportMissing;
|
|
78
88
|
protected render(): TemplateResult;
|
|
79
89
|
}
|
|
80
90
|
declare global {
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
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
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, elementById, HelixElement } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, elementById, HelixElement, reportRuntime } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
|
|
@@ -18,6 +18,7 @@ let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
|
|
|
18
18
|
}
|
|
19
19
|
const target = this.target;
|
|
20
20
|
if (!target) {
|
|
21
|
+
this.reportMissing();
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
event.preventDefault();
|
|
@@ -29,6 +30,7 @@ let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
|
|
|
29
30
|
target.addEventListener('blur', () => target.removeAttribute('tabindex'), { once: true });
|
|
30
31
|
target.focus();
|
|
31
32
|
};
|
|
33
|
+
this.missed = '';
|
|
32
34
|
}
|
|
33
35
|
static { this.shadowRootOptions = {
|
|
34
36
|
...HelixElement.shadowRootOptions,
|
|
@@ -40,6 +42,18 @@ let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
|
|
|
40
42
|
}
|
|
41
43
|
return elementById(this, decodeURIComponent(this.href.slice(1)), { orDocument: true });
|
|
42
44
|
}
|
|
45
|
+
reportMissing() {
|
|
46
|
+
if (!this.href.startsWith('#') || this.href.length < 2 || this.missed === this.href) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.missed = this.href;
|
|
50
|
+
reportRuntime({
|
|
51
|
+
kind: 'missing-target',
|
|
52
|
+
message: `<${this.localName} href="${this.href}"> names no element in its tree, so the press ` +
|
|
53
|
+
'moves focus nowhere. Give the main content that id, or point `href` at the id it has.',
|
|
54
|
+
tag: this.localName,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
43
57
|
render() {
|
|
44
58
|
return html `<a part="link" href=${this.href} @click=${this.skip}><slot>${this.label}</slot></a>`;
|
|
45
59
|
}
|