@fusion.dev/helix-headless 0.143.0 → 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 +8551 -375
- package/dist/area-chart/root/area-chart-root.d.ts +12 -4
- package/dist/area-chart/root/area-chart-root.js +24 -7
- package/dist/avatar/root/avatar-root.d.ts +14 -0
- package/dist/avatar/root/avatar-root.js +23 -9
- package/dist/badge/root/badge-root.d.ts +11 -7
- package/dist/badge/root/badge-root.js +1 -1
- package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
- package/dist/bar-chart/root/bar-chart-root.js +17 -10
- package/dist/button/root/button-root.d.ts +47 -1
- package/dist/button/root/button-root.js +38 -4
- package/dist/callout/root/callout-root.d.ts +18 -1
- package/dist/callout/root/callout-root.js +15 -4
- package/dist/card/root/card-root.d.ts +30 -1
- package/dist/card/root/card-root.js +38 -5
- package/dist/checkbox/root/checkbox-root.js +2 -0
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
- package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
- 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 +30 -0
- package/dist/code-block/root/code-block-root.js +23 -2
- package/dist/color-input/root/color-input-root.d.ts +34 -1
- package/dist/color-input/root/color-input-root.js +23 -4
- 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 +81 -5
- package/dist/color-picker/root/color-picker-root.js +103 -10
- package/dist/combobox/root/combobox-root.d.ts +38 -3
- package/dist/combobox/root/combobox-root.js +64 -2
- package/dist/command/root/command-root.d.ts +30 -4
- package/dist/command/root/command-root.js +33 -3
- 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 +27 -1
- package/dist/copy-button/root/copy-button-root.js +43 -7
- package/dist/date-input/root/date-input-root.d.ts +73 -4
- package/dist/date-input/root/date-input-root.js +93 -43
- package/dist/date-picker/root/date-picker-root.d.ts +145 -1
- package/dist/date-picker/root/date-picker-root.js +239 -48
- package/dist/dialog/description.d.ts +9 -0
- package/dist/dialog/description.js +14 -0
- package/dist/dialog/root/dialog-root.d.ts +64 -1
- package/dist/dialog/root/dialog-root.js +49 -23
- package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
- package/dist/donut-chart/root/donut-chart-root.js +21 -6
- package/dist/drawer/root/drawer-root.d.ts +63 -2
- package/dist/drawer/root/drawer-root.js +95 -19
- package/dist/drawer/swipe-start.d.ts +9 -0
- package/dist/drawer/swipe-start.js +12 -0
- package/dist/element-events.generated.js +8 -5
- package/dist/empty/root/empty-root.d.ts +1 -1
- package/dist/file-input/root/file-input-root.d.ts +47 -0
- package/dist/file-input/root/file-input-root.js +60 -22
- package/dist/file-input/screen.d.ts +35 -0
- package/dist/file-input/screen.js +49 -0
- package/dist/filter-input/root/filter-input-root.d.ts +38 -4
- package/dist/filter-input/root/filter-input-root.js +67 -3
- 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 +51 -0
- package/dist/format-date/root/format-date-root.js +58 -38
- package/dist/heatmap/root/heatmap-root.d.ts +23 -6
- package/dist/heatmap/root/heatmap-root.js +24 -13
- 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 +30 -4
- package/dist/icon/root/icon-root.js +41 -6
- 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/image/root/image-root.d.ts +14 -0
- package/dist/image/root/image-root.js +15 -2
- package/dist/input/root/input-root.d.ts +11 -0
- package/dist/input/root/input-root.js +15 -0
- package/dist/kbd/root/kbd-root.d.ts +20 -13
- package/dist/kbd/root/kbd-root.js +35 -37
- 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 +56 -0
- package/dist/legend/root/legend-root.js +54 -5
- package/dist/lightbox/item/lightbox-item.d.ts +15 -0
- package/dist/lightbox/item/lightbox-item.js +10 -1
- package/dist/lightbox/root/lightbox-root.d.ts +61 -1
- package/dist/lightbox/root/lightbox-root.js +94 -4
- package/dist/line-chart/root/line-chart-root.d.ts +11 -3
- package/dist/line-chart/root/line-chart-root.js +19 -5
- package/dist/listbox/range.d.ts +17 -0
- package/dist/listbox/range.js +5 -0
- package/dist/listbox/root/listbox-root.d.ts +74 -3
- package/dist/listbox/root/listbox-root.js +87 -9
- 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/meter/root/meter-root.d.ts +7 -0
- package/dist/meter/root/meter-root.js +6 -3
- package/dist/notifications/feed-keys.d.ts +10 -0
- package/dist/notifications/feed-keys.js +14 -0
- package/dist/notifications/notification/notification.d.ts +17 -0
- package/dist/notifications/notification/notification.js +21 -2
- package/dist/notifications/root/notifications-root.d.ts +42 -0
- package/dist/notifications/root/notifications-root.js +60 -1
- package/dist/number-input/root/number-input-root.d.ts +47 -0
- package/dist/number-input/root/number-input-root.js +58 -22
- package/dist/object-inspector/root/object-inspector-root.d.ts +1 -0
- package/dist/object-inspector/root/object-inspector-root.js +1 -1
- package/dist/option/root/option-root.d.ts +54 -5
- package/dist/option/root/option-root.js +34 -5
- 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 +21 -0
- package/dist/pin-input/root/pin-input-root.js +29 -2
- package/dist/popover/root/popover-root.d.ts +62 -4
- package/dist/popover/root/popover-root.js +53 -5
- package/dist/progress/root/progress-root.d.ts +7 -0
- package/dist/progress/root/progress-root.js +9 -3
- 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 +26 -0
- package/dist/qr-code/root/qr-code-root.js +49 -2
- package/dist/radio/group/radio-group.d.ts +23 -0
- package/dist/radio/group/radio-group.js +18 -0
- package/dist/rating/root/rating-root.d.ts +25 -0
- package/dist/rating/root/rating-root.js +45 -3
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
- package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
- package/dist/select/root/select-root.d.ts +24 -0
- package/dist/select/root/select-root.js +20 -6
- package/dist/skeleton/root/skeleton-root.d.ts +25 -0
- package/dist/skeleton/root/skeleton-root.js +17 -2
- package/dist/slider/root/slider-root.d.ts +39 -0
- package/dist/slider/root/slider-root.js +45 -9
- package/dist/stat/root/stat-root.d.ts +2 -0
- package/dist/stat/root/stat-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 +12 -2
- package/dist/switch/root/switch-root.js +6 -1
- 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 +60 -0
- package/dist/tag/root/tag-root.js +74 -18
- 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 +9 -0
- package/dist/textarea/root/textarea-root.js +9 -1
- package/dist/time-input/root/time-input-root.d.ts +44 -5
- package/dist/time-input/root/time-input-root.js +69 -39
- package/dist/time-picker/root/time-picker-root.d.ts +54 -8
- package/dist/time-picker/root/time-picker-root.js +121 -91
- 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 +42 -8
- package/dist/toast/root/toast-root.js +64 -4
- package/dist/toggle/group/toggle-group.d.ts +57 -15
- package/dist/toggle/group/toggle-group.js +69 -9
- package/dist/toggle/root/toggle-root.d.ts +12 -1
- package/dist/toggle/root/toggle-root.js +11 -3
- 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 +42 -17
- package/dist/tooltip/root/tooltip-root.js +50 -9
- package/dist/tour/naming.d.ts +30 -0
- package/dist/tour/naming.js +10 -0
- package/dist/tour/root/tour-root.d.ts +35 -0
- package/dist/tour/root/tour-root.js +56 -5
- package/dist/tour/stop/tour-stop.d.ts +53 -8
- package/dist/tour/stop/tour-stop.js +45 -15
- package/dist/tour/wait.d.ts +11 -0
- package/dist/tour/wait.js +19 -0
- package/dist/trend/root/trend-root.d.ts +13 -0
- package/dist/trend/root/trend-root.js +14 -9
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +4862 -254
- package/types/svelte.d.ts +1 -0
- package/types/vue.d.ts +1149 -67
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AriaForwardController, DisabledCascadeController, HelixFormElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import { HelixHeadlessOption } from '../../option/root/option-root.js';
|
|
4
4
|
declare const HelixHeadlessListbox_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
|
|
5
5
|
/**
|
|
@@ -24,10 +24,18 @@ declare const HelixHeadlessListbox_base: (new (...args: any[]) => import("@fusio
|
|
|
24
24
|
* letters move it to the next option whose words start with them. In a
|
|
25
25
|
* single-choice list the highlight is the choice unless `manual` is set; then
|
|
26
26
|
* Enter, Space or a click chooses. With `multiple`, Space toggles the
|
|
27
|
-
* highlighted option, Shift with an arrow adds the option it moves to,
|
|
27
|
+
* highlighted option, Shift with an arrow adds the option it moves to, Shift
|
|
28
|
+
* with Home or End adds every option from the highlight to that end, and
|
|
28
29
|
* Control+A or Command+A chooses every option, or none where all are chosen.
|
|
29
30
|
* In a filter field, Space, Home, End and letters edit the text.
|
|
30
31
|
*
|
|
32
|
+
* An option with `href` is a link: Enter or a press follows it and selects
|
|
33
|
+
* nothing, and moving onto it with the arrows does not follow it.
|
|
34
|
+
*
|
|
35
|
+
* As a filter narrows the list, a screen reader hears how many options are
|
|
36
|
+
* left, such as "3 results", from `result-label` and `results-label`. A filter
|
|
37
|
+
* that leaves none says `empty-text`.
|
|
38
|
+
*
|
|
31
39
|
* @since 0.1.0
|
|
32
40
|
* @status stable
|
|
33
41
|
* @category forms
|
|
@@ -35,10 +43,12 @@ declare const HelixHeadlessListbox_base: (new (...args: any[]) => import("@fusio
|
|
|
35
43
|
* @documentation https://fusion.dev/helix/components/listbox
|
|
36
44
|
*
|
|
37
45
|
* @slot - The options, and any groups of them.
|
|
46
|
+
* @slot empty - What the list shows when it has no options to show, for content richer than `empty-text`.
|
|
38
47
|
* @slot feedback - The message that says what is wrong, for content richer than a string. It shows while there is a message.
|
|
39
48
|
* @slot filter-icon - The icon in the filter field. Defaults to a magnifying glass.
|
|
40
49
|
* @slot hint - The hint, for content richer than plain text.
|
|
41
50
|
* @slot label - The label, for content richer than plain text.
|
|
51
|
+
* @slot loading - What the list shows while its options load, for content richer than `loading-text`.
|
|
42
52
|
*
|
|
43
53
|
* @event {Event} change - Fired after the value changes.
|
|
44
54
|
* @event {HelixBeforeChangeEvent} helix-before-change - Fired when the value is about to change, and cancelable.
|
|
@@ -52,12 +62,14 @@ declare const HelixHeadlessListbox_base: (new (...args: any[]) => import("@fusio
|
|
|
52
62
|
* @csspart hint - Help under the list.
|
|
53
63
|
* @csspart items - The list of options.
|
|
54
64
|
* @csspart label - The label above the list.
|
|
55
|
-
* @csspart live - A copy of the empty and loading text, in a region that screen readers announce when it changes. Visually hidden in the styled tier.
|
|
65
|
+
* @csspart live - A copy of the empty and loading text, and how many options a filter left, in a region that screen readers announce when it changes. Visually hidden in the styled tier.
|
|
56
66
|
* @csspart loading - The text shown while the options load.
|
|
57
67
|
* @csspart search - The region that holds the filter field.
|
|
58
68
|
* @csspart search-field - The filter field.
|
|
59
69
|
*
|
|
70
|
+
* @cssstate empty - The list has no options to show: none were given, or the filter hid every one.
|
|
60
71
|
* @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
|
|
72
|
+
* @cssstate loading - While `loading` is set.
|
|
61
73
|
*/
|
|
62
74
|
export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
63
75
|
protected readonly aria: AriaForwardController;
|
|
@@ -104,6 +116,16 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
104
116
|
* own search boxes would be three copies of one field.
|
|
105
117
|
*/
|
|
106
118
|
filterable: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* How long typing in the filter field rests before the list is filtered, in
|
|
121
|
+
* milliseconds. `0` filters on every keystroke.
|
|
122
|
+
*
|
|
123
|
+
* For a list filtered on a server, where `helix-filter` starts a request: a
|
|
124
|
+
* reader typing a word sends one request for the word, not one for each
|
|
125
|
+
* letter. Enter and the arrow keys filter at once on what is typed, so they
|
|
126
|
+
* act on the list the reader expects.
|
|
127
|
+
*/
|
|
128
|
+
filterDelay: number;
|
|
107
129
|
/**
|
|
108
130
|
* Placeholder text for the filter field.
|
|
109
131
|
*/
|
|
@@ -121,6 +143,20 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
121
143
|
* Whether more than one option can be chosen.
|
|
122
144
|
*/
|
|
123
145
|
multiple: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* What a screen reader hears when a filter leaves one option. `{count}` is
|
|
148
|
+
* the count.
|
|
149
|
+
*/
|
|
150
|
+
resultLabel: string;
|
|
151
|
+
/**
|
|
152
|
+
* What a screen reader hears when a filter leaves any other number of
|
|
153
|
+
* options. `{count}` is the count.
|
|
154
|
+
*
|
|
155
|
+
* **Two sentences, chosen by `Intl.PluralRules`**, which is Data Table's
|
|
156
|
+
* pair: a language with more forms than two picks the one for its `one` and
|
|
157
|
+
* writes the rest here.
|
|
158
|
+
*/
|
|
159
|
+
resultsLabel: string;
|
|
124
160
|
/**
|
|
125
161
|
* Which option the keyboard is on, which is not what is chosen.
|
|
126
162
|
*/
|
|
@@ -189,6 +225,18 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
189
225
|
* ArrowUp, and Combobox moves the highlight off the list onto its create row.
|
|
190
226
|
*/
|
|
191
227
|
highlight(where: 'first' | 'last' | 'none' | 'selected'): void;
|
|
228
|
+
/**
|
|
229
|
+
* Says how many options a filter left, in the live region.
|
|
230
|
+
*
|
|
231
|
+
* **A reader typing into a filter cannot see the list narrow**, and React
|
|
232
|
+
* Aria and Web Awesome both say the count as it changes. Only "No matches"
|
|
233
|
+
* was said here, so a query that left forty options and one that left one
|
|
234
|
+
* sounded the same. Said when the filter changes and the count with it, so
|
|
235
|
+
* a keystroke that leaves the same options says nothing new. An empty filter
|
|
236
|
+
* says nothing, being the list as it was given; no matches is `empty-text`,
|
|
237
|
+
* which the region says in place of this.
|
|
238
|
+
*/
|
|
239
|
+
private tell;
|
|
192
240
|
/**
|
|
193
241
|
* @internal Moves the highlight to the option whose words start with the
|
|
194
242
|
* letters typed, and returns its value, or nothing where none matches.
|
|
@@ -376,6 +424,12 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
376
424
|
* their answer moved when it did not is worse than telling them nothing.
|
|
377
425
|
*/
|
|
378
426
|
private readonly handleFilter;
|
|
427
|
+
/** The filter waiting out `filterDelay`, if any. */
|
|
428
|
+
private readonly filtering;
|
|
429
|
+
/** Filters on what the field holds, and puts the field back where that was refused. */
|
|
430
|
+
private takeFilter;
|
|
431
|
+
/** Filters at once on a filter still waiting out `filterDelay`. */
|
|
432
|
+
private flushFilter;
|
|
379
433
|
/** Requests a new filter, and returns whether the request went through. */
|
|
380
434
|
requestFilter(next: string): boolean;
|
|
381
435
|
/**
|
|
@@ -414,6 +468,23 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
414
468
|
* cannot change.
|
|
415
469
|
*/
|
|
416
470
|
protected renderFilterIcon(): TemplateResult;
|
|
471
|
+
/**
|
|
472
|
+
* Whether there is anything to show.
|
|
473
|
+
*
|
|
474
|
+
* Showing nothing is not the same as empty. A filter that hides every option
|
|
475
|
+
* is empty, and one with no filter and no options is empty too, but a list
|
|
476
|
+
* still waiting on its options is neither.
|
|
477
|
+
*/
|
|
478
|
+
private get showsOptions();
|
|
479
|
+
/**
|
|
480
|
+
* The empty or loading line, with a slot for each so either can be richer
|
|
481
|
+
* than its text.
|
|
482
|
+
*
|
|
483
|
+
* **The text is the slot's fallback**, which is this library's rule for a
|
|
484
|
+
* content property, so `empty-text` still shows where nothing is slotted and
|
|
485
|
+
* the live region still says it.
|
|
486
|
+
*/
|
|
487
|
+
protected renderStatus(hasContent: boolean): TemplateResult | typeof nothing;
|
|
417
488
|
protected render(): TemplateResult;
|
|
418
489
|
}
|
|
419
490
|
declare global {
|
|
@@ -4,13 +4,14 @@ 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, DisabledCascadeController, HelixFilterEvent, HelixFormElement, hostAria, MixinCollection, MixinControllerValue, MixinField, reportValues, requestChange, scrollItemIntoView, Typeahead, typesCharacter, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, countLabel, customElement, DelayController, DisabledCascadeController, HelixFilterEvent, HelixFormElement, hostAria, MixinCollection, MixinControllerValue, MixinField, reportValues, requestChange, scrollItemIntoView, Typeahead, typesCharacter, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { magnifyingGlass } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
12
|
import { HelixHeadlessOptionGroup } from '../../option/group/option-group.js';
|
|
13
13
|
import { HelixHeadlessOption } from '../../option/root/option-root.js';
|
|
14
|
+
import { rangeAdd } from '../range.js';
|
|
14
15
|
const MOVES = new Set(['ArrowDown', 'ArrowUp', 'End', 'Home', 'PageDown', 'PageUp']);
|
|
15
16
|
const FIELD_MOVES = new Set(['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp']);
|
|
16
17
|
const PAGE = 10;
|
|
@@ -68,9 +69,12 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
68
69
|
this.emptyText = 'No matches';
|
|
69
70
|
this.filterState = undefined;
|
|
70
71
|
this.filterable = false;
|
|
72
|
+
this.filterDelay = 0;
|
|
71
73
|
this.filterLabel = 'Filter';
|
|
72
74
|
this.manual = false;
|
|
73
75
|
this.multiple = false;
|
|
76
|
+
this.resultLabel = '{count} result';
|
|
77
|
+
this.resultsLabel = '{count} results';
|
|
74
78
|
this.activeValue = '';
|
|
75
79
|
this.parked = false;
|
|
76
80
|
this.typeahead = new Typeahead();
|
|
@@ -82,6 +86,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
82
86
|
items: () => this.options,
|
|
83
87
|
});
|
|
84
88
|
this.handleSelection = (event) => {
|
|
89
|
+
if (event.detail.href !== undefined) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
85
92
|
event.stopPropagation();
|
|
86
93
|
if (event.defaultPrevented) {
|
|
87
94
|
return;
|
|
@@ -96,7 +103,14 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
96
103
|
}
|
|
97
104
|
const typing = this.forwarded || event.composedPath()[0] === this.partNamed('search-field');
|
|
98
105
|
if (event.key === 'Enter' || (event.key === ' ' && !typing && !this.typeahead.typing())) {
|
|
99
|
-
|
|
106
|
+
const link = shown.find((option) => option.value === this.activeValue && Boolean(option.href));
|
|
107
|
+
if (link !== undefined) {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
if (event.key === 'Enter') {
|
|
110
|
+
link.select(event);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (this.activeValue !== '') {
|
|
100
114
|
event.preventDefault();
|
|
101
115
|
this.choose(this.activeValue);
|
|
102
116
|
}
|
|
@@ -155,6 +169,14 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
155
169
|
const target = shown[next];
|
|
156
170
|
if (target) {
|
|
157
171
|
this.arrive(target.value);
|
|
172
|
+
const ranged = event.key === 'Home' || event.key === 'End';
|
|
173
|
+
if (this.multiple && !typing && event.shiftKey && ranged) {
|
|
174
|
+
const taken = rangeAdd(this.values, shown.map((option) => option.value), at, next);
|
|
175
|
+
if (taken) {
|
|
176
|
+
this.requestValue(taken.join(','));
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
158
180
|
if (this.multiple && !typing && event.shiftKey && !this.values.includes(target.value)) {
|
|
159
181
|
this.choose(target.value);
|
|
160
182
|
}
|
|
@@ -163,16 +185,27 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
163
185
|
this.handleFilter = (event) => {
|
|
164
186
|
event.stopPropagation();
|
|
165
187
|
const control = event.target;
|
|
166
|
-
this.
|
|
167
|
-
if (
|
|
168
|
-
|
|
188
|
+
this.filtering.cancel();
|
|
189
|
+
if (this.filterDelay > 0) {
|
|
190
|
+
this.filtering.start(this.filterDelay, () => this.takeFilter(control));
|
|
191
|
+
return;
|
|
169
192
|
}
|
|
193
|
+
this.takeFilter(control);
|
|
170
194
|
};
|
|
195
|
+
this.filtering = new DelayController(this, { renders: false });
|
|
171
196
|
this.handleFilterKeydown = (event) => {
|
|
172
|
-
if (event.key === '
|
|
197
|
+
if (event.key === 'Enter' || FIELD_MOVES.has(event.key)) {
|
|
198
|
+
this.flushFilter();
|
|
199
|
+
}
|
|
200
|
+
if (event.key === 'Escape' && (this.filter !== '' || this.filtering.pending)) {
|
|
173
201
|
event.stopPropagation();
|
|
174
202
|
event.preventDefault();
|
|
203
|
+
this.filtering.cancel();
|
|
175
204
|
this.requestFilter('');
|
|
205
|
+
const control = this.partNamed('search-field');
|
|
206
|
+
if (control && control.value !== this.filter) {
|
|
207
|
+
control.value = this.filter;
|
|
208
|
+
}
|
|
176
209
|
return;
|
|
177
210
|
}
|
|
178
211
|
if (!FIELD_MOVES.has(event.key)) {
|
|
@@ -218,6 +251,17 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
218
251
|
this.place();
|
|
219
252
|
}
|
|
220
253
|
}
|
|
254
|
+
tell(changed) {
|
|
255
|
+
if (!changed.has('filter') || !this.hasUpdated) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const count = this.shown.length;
|
|
259
|
+
if (this.filter.trim() === '' || count === 0) {
|
|
260
|
+
this.announced = '';
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.announced = countLabel(count, this.resultLabel, this.resultsLabel, this.lang);
|
|
264
|
+
}
|
|
221
265
|
typeaheadTo(key) {
|
|
222
266
|
const shown = this.shown.filter((option) => !option.disabled);
|
|
223
267
|
const from = shown.findIndex((option) => option.value === this.currentValue);
|
|
@@ -294,7 +338,8 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
294
338
|
arrive(value) {
|
|
295
339
|
this.parked = false;
|
|
296
340
|
this.activeValue = value;
|
|
297
|
-
|
|
341
|
+
const link = this.options.some((option) => option.value === value && Boolean(option.href));
|
|
342
|
+
if (!this.multiple && !this.manual && !this.forwarded && !link) {
|
|
298
343
|
this.choose(value);
|
|
299
344
|
}
|
|
300
345
|
}
|
|
@@ -423,7 +468,10 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
423
468
|
}
|
|
424
469
|
if (this.childrenKnown) {
|
|
425
470
|
this.place();
|
|
471
|
+
this.tell(changed);
|
|
426
472
|
}
|
|
473
|
+
this.toggleState('empty', !this.showsOptions);
|
|
474
|
+
this.toggleState('loading', this.loading);
|
|
427
475
|
}
|
|
428
476
|
seedDeclared() {
|
|
429
477
|
if (this.owned || this.defaultValue !== '' || this.hasAttribute('default-value')) {
|
|
@@ -453,6 +501,16 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
453
501
|
this.sync();
|
|
454
502
|
this.linkActive();
|
|
455
503
|
}
|
|
504
|
+
takeFilter(control) {
|
|
505
|
+
this.filtering.cancel();
|
|
506
|
+
this.requestFilter(control.value);
|
|
507
|
+
if (control.value !== this.filter) {
|
|
508
|
+
control.value = this.filter;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
flushFilter() {
|
|
512
|
+
this.filtering.flush();
|
|
513
|
+
}
|
|
456
514
|
requestFilter(next) {
|
|
457
515
|
if (next === this.filter) {
|
|
458
516
|
return true;
|
|
@@ -480,12 +538,23 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
480
538
|
</svg>
|
|
481
539
|
</slot>`;
|
|
482
540
|
}
|
|
483
|
-
|
|
484
|
-
|
|
541
|
+
get showsOptions() {
|
|
542
|
+
return this.counted && !this.loading
|
|
485
543
|
? this.filter === ''
|
|
486
544
|
? this.options.length > 0
|
|
487
545
|
: this.hasMatches
|
|
488
546
|
: true;
|
|
547
|
+
}
|
|
548
|
+
renderStatus(hasContent) {
|
|
549
|
+
if (!this.loading && hasContent) {
|
|
550
|
+
return nothing;
|
|
551
|
+
}
|
|
552
|
+
return this.loading
|
|
553
|
+
? html `<p part="loading"><slot name="loading">${this.loadingText}</slot></p>`
|
|
554
|
+
: html `<p part="empty"><slot name="empty">${this.emptyText}</slot></p>`;
|
|
555
|
+
}
|
|
556
|
+
render() {
|
|
557
|
+
const shown = this.showsOptions;
|
|
489
558
|
return html `${this.renderGroupLabel()}
|
|
490
559
|
${this.filterable
|
|
491
560
|
? html `<div part="search">
|
|
@@ -542,6 +611,9 @@ __decorate([
|
|
|
542
611
|
__decorate([
|
|
543
612
|
property({ attribute: 'filterable', reflect: true, type: Boolean })
|
|
544
613
|
], HelixHeadlessListbox.prototype, "filterable", void 0);
|
|
614
|
+
__decorate([
|
|
615
|
+
property({ attribute: 'filter-delay', type: Number })
|
|
616
|
+
], HelixHeadlessListbox.prototype, "filterDelay", void 0);
|
|
545
617
|
__decorate([
|
|
546
618
|
property({ attribute: 'filter-label' })
|
|
547
619
|
], HelixHeadlessListbox.prototype, "filterLabel", void 0);
|
|
@@ -551,6 +623,12 @@ __decorate([
|
|
|
551
623
|
__decorate([
|
|
552
624
|
property({ reflect: true, type: Boolean })
|
|
553
625
|
], HelixHeadlessListbox.prototype, "multiple", void 0);
|
|
626
|
+
__decorate([
|
|
627
|
+
property({ attribute: 'result-label' })
|
|
628
|
+
], HelixHeadlessListbox.prototype, "resultLabel", void 0);
|
|
629
|
+
__decorate([
|
|
630
|
+
property({ attribute: 'results-label' })
|
|
631
|
+
], HelixHeadlessListbox.prototype, "resultsLabel", void 0);
|
|
554
632
|
__decorate([
|
|
555
633
|
state()
|
|
556
634
|
], HelixHeadlessListbox.prototype, "activeValue", void 0);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks a region busy for as long as something is loading into it, and
|
|
3
|
+
* counts, so two loaders in one region do not clear each other.
|
|
4
|
+
*
|
|
5
|
+
* Beside the family's `index.ts`, because which element is busy and how many
|
|
6
|
+
* claims it holds is bookkeeping about elements, not about a loader's
|
|
7
|
+
* template.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Adds a claim on the region, setting `aria-busy="true"` on the first.
|
|
11
|
+
*
|
|
12
|
+
* Only an `aria-busy` this file wrote is ever taken off again: a region the
|
|
13
|
+
* page marked busy itself keeps its own attribute.
|
|
14
|
+
*/
|
|
15
|
+
export declare const claimBusy: (region: Element) => void;
|
|
16
|
+
/** Takes a claim back, and the attribute with the last one. */
|
|
17
|
+
export declare const releaseBusy: (region: Element) => void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const CLAIMS = new WeakMap();
|
|
2
|
+
export const claimBusy = (region) => {
|
|
3
|
+
const held = CLAIMS.get(region) ?? 0;
|
|
4
|
+
if (held === 0 && region.getAttribute('aria-busy') === 'true') {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
CLAIMS.set(region, held + 1);
|
|
8
|
+
if (held === 0) {
|
|
9
|
+
region.setAttribute('aria-busy', 'true');
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export const releaseBusy = (region) => {
|
|
13
|
+
const held = CLAIMS.get(region);
|
|
14
|
+
if (held === undefined) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (held <= 1) {
|
|
18
|
+
CLAIMS.delete(region);
|
|
19
|
+
region.removeAttribute('aria-busy');
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
CLAIMS.set(region, held - 1);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -19,6 +19,15 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
19
19
|
* a `role` attribute on the host would be missing from React server-rendered
|
|
20
20
|
* markup.
|
|
21
21
|
*
|
|
22
|
+
* **`for` names the region being loaded**, and the loader sets
|
|
23
|
+
* `aria-busy="true"` on it for as long as the loader is on the page, so a
|
|
24
|
+
* screen reader waits for the region rather than reading it half filled. Two
|
|
25
|
+
* loaders in one region hold it busy until the last one leaves.
|
|
26
|
+
*
|
|
27
|
+
* **`delay` keeps a quick load from flashing a loader.** The loader stays
|
|
28
|
+
* hidden, and silent, for that many milliseconds, and appears only if the work
|
|
29
|
+
* is still going.
|
|
30
|
+
*
|
|
22
31
|
* @since 0.1.0
|
|
23
32
|
* @status stable
|
|
24
33
|
* @category feedback
|
|
@@ -33,11 +42,39 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
33
42
|
* @csspart track - The full circle behind the indicator.
|
|
34
43
|
*/
|
|
35
44
|
export declare class HelixHeadlessLoader extends HelixElement {
|
|
45
|
+
/**
|
|
46
|
+
* How long the loader waits before it shows, in milliseconds, so work that
|
|
47
|
+
* finishes quickly never flashes one.
|
|
48
|
+
*
|
|
49
|
+
* Absent shows it at once. Hidden in the served markup too, so a page that
|
|
50
|
+
* loads quickly never draws it.
|
|
51
|
+
*/
|
|
52
|
+
delay: number;
|
|
53
|
+
/**
|
|
54
|
+
* The id of the region being loaded, which is marked `aria-busy` while the
|
|
55
|
+
* loader is on the page.
|
|
56
|
+
*
|
|
57
|
+
* Names an element in the same tree. In React it arrives as a property, the
|
|
58
|
+
* way `Tooltip.for` does.
|
|
59
|
+
*/
|
|
60
|
+
for: string;
|
|
36
61
|
/**
|
|
37
62
|
* The loader's name, announced by screen readers while the work is in
|
|
38
63
|
* progress.
|
|
39
64
|
*/
|
|
40
65
|
label: string;
|
|
66
|
+
/** The wait `delay` asks for, and whether it has run out. */
|
|
67
|
+
private readonly due;
|
|
68
|
+
/** The region this loader marked busy, so it is the one released. */
|
|
69
|
+
private region;
|
|
70
|
+
/** Whether the loader is still waiting out its `delay`. */
|
|
71
|
+
private get waiting();
|
|
72
|
+
connectedCallback(): void;
|
|
73
|
+
disconnectedCallback(): void;
|
|
74
|
+
/** Marks the region `for` names busy, releasing one it named before. */
|
|
75
|
+
private mark;
|
|
76
|
+
private release;
|
|
77
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
41
78
|
protected willUpdate(changed: PropertyValues): void;
|
|
42
79
|
protected render(): TemplateResult;
|
|
43
80
|
}
|
|
@@ -4,27 +4,76 @@ 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, hostAria } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, DelayController, elementById, HelixElement, hostAria, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
+
import { claimBusy, releaseBusy } from '../busy.js';
|
|
10
11
|
let HelixHeadlessLoader = class HelixHeadlessLoader extends HelixElement {
|
|
11
12
|
constructor() {
|
|
12
13
|
super(...arguments);
|
|
14
|
+
this.delay = 0;
|
|
15
|
+
this.for = '';
|
|
13
16
|
this.label = 'Loading';
|
|
17
|
+
this.due = new DelayController(this);
|
|
18
|
+
this.region = null;
|
|
19
|
+
}
|
|
20
|
+
get waiting() {
|
|
21
|
+
return this.delay > 0 && !this.due.elapsed;
|
|
22
|
+
}
|
|
23
|
+
connectedCallback() {
|
|
24
|
+
super.connectedCallback();
|
|
25
|
+
if (this.delay > 0 && !this.due.elapsed) {
|
|
26
|
+
this.due.start(this.delay);
|
|
27
|
+
}
|
|
28
|
+
requestAnimationFrame(() => this.mark());
|
|
29
|
+
}
|
|
30
|
+
disconnectedCallback() {
|
|
31
|
+
super.disconnectedCallback();
|
|
32
|
+
this.release();
|
|
33
|
+
}
|
|
34
|
+
mark() {
|
|
35
|
+
const region = this.isConnected && this.for !== '' ? elementById(this, this.for) : null;
|
|
36
|
+
if (region === this.region) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.release();
|
|
40
|
+
if (region) {
|
|
41
|
+
claimBusy(region);
|
|
42
|
+
this.region = region;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
release() {
|
|
46
|
+
if (this.region) {
|
|
47
|
+
releaseBusy(this.region);
|
|
48
|
+
this.region = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
updated(changed) {
|
|
52
|
+
super.updated(changed);
|
|
53
|
+
if (changed.has('for') && changed.get('for') !== undefined) {
|
|
54
|
+
this.mark();
|
|
55
|
+
}
|
|
14
56
|
}
|
|
15
57
|
willUpdate(changed) {
|
|
16
58
|
super.willUpdate(changed);
|
|
17
59
|
hostAria(this.internals, { label: this.label, role: 'progressbar' });
|
|
18
60
|
this.internals.ariaBusy = 'true';
|
|
61
|
+
this.internals.ariaHidden = this.waiting ? 'true' : null;
|
|
19
62
|
}
|
|
20
63
|
render() {
|
|
21
|
-
return html `<svg part="svg" viewBox="0 0 24 24" aria-hidden="true">
|
|
64
|
+
return html `<svg part="svg" viewBox="0 0 24 24" aria-hidden="true" ?hidden=${this.waiting}>
|
|
22
65
|
<circle part="track" cx="12" cy="12" r="10" fill="none" />
|
|
23
66
|
<circle part="indicator" cx="12" cy="12" r="10" fill="none" />
|
|
24
67
|
</svg>
|
|
25
|
-
<span part="mark" aria-hidden="true"><slot></slot></span>`;
|
|
68
|
+
<span part="mark" aria-hidden="true" ?hidden=${this.waiting}><slot></slot></span>`;
|
|
26
69
|
}
|
|
27
70
|
};
|
|
71
|
+
__decorate([
|
|
72
|
+
property({ type: Number })
|
|
73
|
+
], HelixHeadlessLoader.prototype, "delay", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
property()
|
|
76
|
+
], HelixHeadlessLoader.prototype, "for", void 0);
|
|
28
77
|
__decorate([
|
|
29
78
|
property()
|
|
30
79
|
], HelixHeadlessLoader.prototype, "label", void 0);
|
|
@@ -22,6 +22,8 @@ export type MeterBand = 'fair' | 'good' | 'poor';
|
|
|
22
22
|
* @aliases Gauge, Level, Capacity
|
|
23
23
|
* @documentation https://fusion.dev/helix/components/meter
|
|
24
24
|
*
|
|
25
|
+
* @slot label - A visible label. Its words name the meter in place of `label`.
|
|
26
|
+
*
|
|
25
27
|
* @csspart groove - The unfilled circle, in `ring` and `arc`.
|
|
26
28
|
* @csspart indicator - The part of the track the value fills.
|
|
27
29
|
* @csspart indicator-fair - The fill while the reading is in a band next to the good band. Set alongside `indicator`.
|
|
@@ -77,6 +79,9 @@ export declare class HelixHeadlessMeter extends HelixElement {
|
|
|
77
79
|
high?: number;
|
|
78
80
|
/**
|
|
79
81
|
* Accessible name for the reading.
|
|
82
|
+
*
|
|
83
|
+
* A visible label in the `label` slot names it instead, with the words it
|
|
84
|
+
* shows, so the name and the label cannot disagree.
|
|
80
85
|
*/
|
|
81
86
|
label: string;
|
|
82
87
|
/**
|
|
@@ -218,6 +223,8 @@ export declare class HelixHeadlessMeter extends HelixElement {
|
|
|
218
223
|
* where the arc starts.
|
|
219
224
|
*/
|
|
220
225
|
private renderRound;
|
|
226
|
+
/** A label's words changed, so the name is written again. */
|
|
227
|
+
private readonly renamed;
|
|
221
228
|
protected render(): TemplateResult;
|
|
222
229
|
}
|
|
223
230
|
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, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixElement, hostAria, slottedWords } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing, svg } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
const percents = new Map();
|
|
@@ -29,6 +29,9 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
29
29
|
this.showValue = false;
|
|
30
30
|
this.value = 0;
|
|
31
31
|
this.valueText = '';
|
|
32
|
+
this.renamed = () => {
|
|
33
|
+
this.requestUpdate();
|
|
34
|
+
};
|
|
32
35
|
}
|
|
33
36
|
get span() {
|
|
34
37
|
const min = Number.isFinite(this.min) ? this.min : 0;
|
|
@@ -82,7 +85,7 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
82
85
|
willUpdate(changed) {
|
|
83
86
|
super.willUpdate(changed);
|
|
84
87
|
const { max, min } = this.span;
|
|
85
|
-
hostAria(this.internals, { label: this.label, role: 'meter' });
|
|
88
|
+
hostAria(this.internals, { label: slottedWords(this, ['label']) || this.label, role: 'meter' });
|
|
86
89
|
this.internals.ariaValueMin = String(min);
|
|
87
90
|
this.internals.ariaValueMax = String(max);
|
|
88
91
|
this.internals.ariaValueNow = String(this.reading);
|
|
@@ -121,7 +124,7 @@ let HelixHeadlessMeter = class HelixHeadlessMeter extends HelixElement {
|
|
|
121
124
|
const beyond = this.beyond;
|
|
122
125
|
const { max, min } = this.span;
|
|
123
126
|
const at = (value) => `${((value - min) / (max - min)) * 100}%`;
|
|
124
|
-
return html
|
|
127
|
+
return html `<slot name="label" @slotchange=${this.renamed}></slot>${this.shape
|
|
125
128
|
? this.renderRound()
|
|
126
129
|
: html `<div part="track">
|
|
127
130
|
${bounds
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a key moves focus in a feed of `count` rows, from the row at `at`, or
|
|
3
|
+
* `undefined` for a key the feed does not take.
|
|
4
|
+
*
|
|
5
|
+
* `onRow` says whether focus is on the row's own box. The arrows and `Home` and
|
|
6
|
+
* `End` move only from there, so a control inside a row keeps its keys.
|
|
7
|
+
* `PageDown` and `PageUp` move from anywhere in a row, which is how the APG feed
|
|
8
|
+
* pattern moves on from inside an article. The answer stays inside the feed.
|
|
9
|
+
*/
|
|
10
|
+
export declare const feedStep: (key: string, at: number, count: number, onRow: boolean) => number | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const feedStep = (key, at, count, onRow) => {
|
|
2
|
+
if (count <= 0) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
const anywhere = { PageDown: at + 1, PageUp: at - 1 };
|
|
6
|
+
const fromRow = {
|
|
7
|
+
ArrowDown: at + 1,
|
|
8
|
+
ArrowUp: at - 1,
|
|
9
|
+
End: count - 1,
|
|
10
|
+
Home: 0,
|
|
11
|
+
};
|
|
12
|
+
const to = anywhere[key] ?? (onRow ? fromRow[key] : undefined);
|
|
13
|
+
return to === undefined ? undefined : Math.max(0, Math.min(to, count - 1));
|
|
14
|
+
};
|
|
@@ -79,6 +79,12 @@ export declare class HelixHeadlessNotification extends HelixHeadlessNotification
|
|
|
79
79
|
* Empty is the runtime's own.
|
|
80
80
|
*/
|
|
81
81
|
locale: string;
|
|
82
|
+
/**
|
|
83
|
+
* @internal Set by the feed: this row's place in it, counting from one, read
|
|
84
|
+
* as `aria-posinset`. Zero leaves the place to the browser, which counts the
|
|
85
|
+
* rows it can see.
|
|
86
|
+
*/
|
|
87
|
+
position: number;
|
|
82
88
|
/**
|
|
83
89
|
* Whether the notification has been read.
|
|
84
90
|
*
|
|
@@ -96,6 +102,17 @@ export declare class HelixHeadlessNotification extends HelixHeadlessNotification
|
|
|
96
102
|
*/
|
|
97
103
|
get read(): boolean;
|
|
98
104
|
set read(next: boolean);
|
|
105
|
+
/**
|
|
106
|
+
* @internal Set by the feed: how many rows the feed holds in all, read as
|
|
107
|
+
* `aria-setsize`, and `-1` when nobody knows. Zero leaves it to the browser.
|
|
108
|
+
*/
|
|
109
|
+
setSize: number;
|
|
110
|
+
/**
|
|
111
|
+
* @internal Set by the feed: whether `Tab` stops on this row. Every row is a
|
|
112
|
+
* stop until the feed has chosen one, which is also what a server sends, so
|
|
113
|
+
* the first render on the client is the server's.
|
|
114
|
+
*/
|
|
115
|
+
stop: boolean;
|
|
99
116
|
/**
|
|
100
117
|
* What a screen reader hears before an unread notification's text.
|
|
101
118
|
*
|