@fusion.dev/helix-core 0.147.0 → 0.149.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 +16160 -12084
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +1 -1
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +2 -2
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +5 -4
- package/dist/controllers/children-controller/children-controller.d.ts +28 -0
- package/dist/controllers/children-controller/children-controller.js +18 -0
- package/dist/controllers/countdown-controller/countdown-controller.d.ts +15 -1
- package/dist/controllers/countdown-controller/countdown-controller.js +27 -2
- package/dist/controllers/floating-controller/floating-controller.d.ts +12 -1
- package/dist/controllers/floating-controller/floating-controller.js +2 -1
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +25 -0
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +8 -0
- package/dist/controllers/machine-controller/machine-controller.d.ts +69 -0
- package/dist/controllers/machine-controller/machine-controller.js +78 -0
- package/dist/controllers/machine-controller/machine.d.ts +93 -0
- package/dist/controllers/machine-controller/machine.js +20 -0
- package/dist/controllers/open-phase-controller/open-phase-controller.d.ts +10 -0
- package/dist/controllers/open-phase-controller/open-phase-controller.js +7 -4
- package/dist/controllers/option-window-controller/option-window-controller.d.ts +96 -0
- package/dist/controllers/option-window-controller/option-window-controller.js +81 -0
- package/dist/controllers/popover-controller/popover-controller.js +1 -1
- package/dist/controllers/reorder-controller/reorder-controller.d.ts +285 -0
- package/dist/controllers/reorder-controller/reorder-controller.js +497 -0
- package/dist/controllers/source-controller/source-controller.js +1 -1
- package/dist/controllers/virtualize-controller/heights.js +1 -1
- package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +117 -3
- package/dist/controllers/virtualize-controller/virtualize-controller.js +134 -32
- package/dist/events/before-reorder.d.ts +14 -0
- package/dist/events/before-reorder.js +11 -0
- package/dist/events/details.d.ts +28 -0
- package/dist/events/events.d.ts +4 -2
- package/dist/events/events.js +3 -1
- package/dist/events/names.js +1 -1
- package/dist/events/reorder.d.ts +18 -12
- package/dist/events/reordered.d.ts +4 -4
- package/dist/helix-element/helix-element.d.ts +14 -5
- package/dist/helix-element/helix-element.js +6 -3
- package/dist/helix-element/react-audit-off.d.ts +11 -0
- package/dist/helix-element/react-audit-off.js +1 -0
- package/dist/helix-element/react-audit.d.ts +20 -0
- package/dist/helix-element/react-audit.js +56 -0
- package/dist/helix-element/react-bridge.d.ts +2 -22
- package/dist/helix-element/react-bridge.js +1 -56
- package/dist/index.d.ts +34 -14
- package/dist/index.js +32 -12
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +7 -2
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +6 -0
- package/dist/mixins/mixin-collection/mixin-collection.js +4 -0
- package/dist/mixins/mixin-dismissible/mixin-dismissible.js +3 -0
- package/dist/mixins/mixin-field/mixin-field.js +1 -1
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +12 -0
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +15 -2
- package/dist/mixins/mixin-tokens/mixin-tokens.d.ts +5 -0
- package/dist/mixins/mixin-tokens/mixin-tokens.js +13 -2
- package/dist/mixins/mixin-viewport/mixin-viewport.d.ts +17 -15
- package/dist/mixins/mixin-viewport/mixin-viewport.js +23 -41
- package/dist/utils/assign-slots/assign-slots.d.ts +18 -0
- package/dist/utils/assign-slots/assign-slots.js +14 -0
- package/dist/utils/autofill-select/autofill-select.d.ts +40 -0
- package/dist/utils/autofill-select/autofill-select.js +14 -0
- package/dist/utils/calendar/calendar.d.ts +27 -0
- package/dist/utils/calendar/calendar.js +17 -0
- package/dist/utils/center-in-view/center-in-view.d.ts +18 -0
- package/dist/utils/center-in-view/center-in-view.js +14 -0
- package/dist/utils/chord/key-names.d.ts +30 -0
- package/dist/utils/chord/key-names.js +52 -0
- package/dist/utils/column-step/column-step.d.ts +13 -0
- package/dist/utils/column-step/column-step.js +9 -0
- package/dist/utils/count-label/count-label.d.ts +7 -4
- package/dist/utils/count-label/count-label.js +2 -5
- package/dist/utils/data-options/data-options.d.ts +60 -0
- package/dist/utils/data-options/data-options.js +46 -0
- package/dist/utils/date-segments/date-segments.d.ts +2 -1
- package/dist/utils/date-segments/date-segments.js +10 -0
- package/dist/utils/draw-tooltip/draw-tooltip.d.ts +7 -2
- package/dist/utils/draw-tooltip/draw-tooltip.js +1 -1
- package/dist/utils/draw-tooltip/spoken-reading.d.ts +3 -2
- package/dist/utils/draw-tooltip/spoken-reading.js +3 -2
- package/dist/utils/export-parts/export-parts.d.ts +14 -0
- package/dist/utils/export-parts/export-parts.js +1 -0
- package/dist/utils/fill-template/fill-template.d.ts +9 -0
- package/dist/utils/fill-template/fill-template.js +3 -0
- package/dist/utils/filter-marks/filter-marks.d.ts +36 -0
- package/dist/utils/filter-marks/filter-marks.js +41 -0
- package/dist/utils/focus-target/focus-target.d.ts +12 -0
- package/dist/utils/focus-target/focus-target.js +9 -0
- package/dist/utils/focusable-elements/focusable-elements.d.ts +17 -0
- package/dist/utils/focusable-elements/focusable-elements.js +8 -0
- package/dist/utils/grid-move/grid-move.d.ts +56 -0
- package/dist/utils/grid-move/grid-move.js +28 -0
- package/dist/utils/locale-of/locale-of.d.ts +5 -0
- package/dist/utils/locale-of/locale-of.js +8 -2
- package/dist/utils/number-format/number-format.d.ts +7 -0
- package/dist/utils/number-format/number-format.js +1 -0
- package/dist/utils/reorder/drag-preview.d.ts +46 -0
- package/dist/utils/reorder/drag-preview.js +58 -0
- package/dist/utils/reorder/reorder.d.ts +178 -0
- package/dist/utils/reorder/reorder.js +112 -0
- package/dist/utils/report-runtime/report-runtime.d.ts +14 -5
- package/dist/utils/report-values/report-values.d.ts +16 -4
- package/dist/utils/report-values/report-values.js +2 -2
- package/dist/utils/request-change/request-change.d.ts +11 -0
- package/dist/utils/request-change/request-change.js +10 -0
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.d.ts +17 -0
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +1 -0
- package/dist/utils/slot-filled/slot-filled.d.ts +8 -0
- package/dist/utils/slot-filled/slot-filled.js +1 -0
- package/dist/utils/slotted-words/slotted-words.d.ts +3 -2
- package/dist/utils/slotted-words/slotted-words.js +3 -1
- package/dist/utils/toolbar-around/toolbar-around.d.ts +18 -0
- package/dist/utils/toolbar-around/toolbar-around.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/words/index.d.ts +3 -0
- package/dist/words/index.js +3 -0
- package/dist/words/message.d.ts +47 -0
- package/dist/words/message.js +152 -0
- package/dist/words/registry.d.ts +89 -0
- package/dist/words/registry.js +101 -0
- package/dist/words/words-locale.d.ts +34 -0
- package/dist/words/words-locale.js +30 -0
- package/dist/words/words.d.ts +52 -0
- package/dist/words/words.js +100 -0
- package/package.json +40 -25
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** One option of a list fed as data, as the list reads and marks it. */
|
|
2
|
+
export interface DataOption {
|
|
3
|
+
/** Whether the keyboard is on it. The list writes this. */
|
|
4
|
+
active: boolean;
|
|
5
|
+
/** Whether it can be chosen. */
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
/** Whether a filter hides it. The list writes this. */
|
|
8
|
+
hidden: boolean;
|
|
9
|
+
/** Whether it is chosen. The list writes this. */
|
|
10
|
+
selected: boolean;
|
|
11
|
+
/** The words a reader sees, which the filter and the typeahead match. */
|
|
12
|
+
text: string;
|
|
13
|
+
/** What choosing it puts in the value. */
|
|
14
|
+
value: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An option as a page hands it over: its value alone, which is also its words,
|
|
18
|
+
* or a record with a `value`, the `label` a reader sees (the value where it is
|
|
19
|
+
* left out) and `disabled`.
|
|
20
|
+
*/
|
|
21
|
+
export type DataOptionItem = number | string | {
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
label?: string;
|
|
24
|
+
value: number | string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The options a page handed over as data, as a list reads them, built once per
|
|
28
|
+
* array.
|
|
29
|
+
*
|
|
30
|
+
* Values and words are strings, as an option element's are, so a number and
|
|
31
|
+
* its digits choose the same option.
|
|
32
|
+
*/
|
|
33
|
+
export declare const dataOptions: (items: readonly DataOptionItem[]) => DataOption[];
|
|
34
|
+
/**
|
|
35
|
+
* The positions a window draws, in order, with `kept` added where it lies
|
|
36
|
+
* outside the window: before it where it comes first, after it where it comes
|
|
37
|
+
* later. `-1` keeps nothing. Positions past `count` are left out.
|
|
38
|
+
*
|
|
39
|
+
* `kept` is the option the keyboard is on. **It has to be drawn wherever it
|
|
40
|
+
* is**, because `aria-activedescendant` points at an element, and an option
|
|
41
|
+
* that is not drawn is one a screen reader cannot be told about.
|
|
42
|
+
* `VirtualizeController.spacers(kept)` takes its room out of the spacer on its
|
|
43
|
+
* side, so the scrollbar still measures the set.
|
|
44
|
+
*/
|
|
45
|
+
export declare const windowOf: (first: number, last: number, count: number, kept: number) => number[];
|
|
46
|
+
/**
|
|
47
|
+
* Draws `rows[index]` for each of `at` into `box`, as elements named `tag`,
|
|
48
|
+
* and returns them in order.
|
|
49
|
+
*
|
|
50
|
+
* **Reused by position.** A scroll moves the window by a row or two at a time,
|
|
51
|
+
* so the elements already drawn take the next rows' words and state rather than
|
|
52
|
+
* being thrown away: a list of ten thousand keeps the twenty or so elements a
|
|
53
|
+
* screen holds.
|
|
54
|
+
*
|
|
55
|
+
* Each element carries its place in the whole set, `aria-posinset` and
|
|
56
|
+
* `aria-setsize`, so a reader on the four-thousandth of ten thousand hears
|
|
57
|
+
* that, and not its place in what happens to be drawn. `disabled` holds every
|
|
58
|
+
* row down, for a list that is disabled itself.
|
|
59
|
+
*/
|
|
60
|
+
export declare const drawOptions: (box: Element, tag: string, rows: readonly DataOption[], at: readonly number[], disabled: boolean) => HTMLElement[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const dataOptions = (items) => items.map((item) => {
|
|
2
|
+
const record = typeof item === 'object' ? item : { value: item };
|
|
3
|
+
return {
|
|
4
|
+
active: false,
|
|
5
|
+
disabled: Boolean(record.disabled),
|
|
6
|
+
hidden: false,
|
|
7
|
+
selected: false,
|
|
8
|
+
text: String(record.label ?? record.value),
|
|
9
|
+
value: String(record.value),
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
export const windowOf = (first, last, count, kept) => {
|
|
13
|
+
const at = [];
|
|
14
|
+
for (let index = first; index < Math.min(last, count); index += 1) {
|
|
15
|
+
at.push(index);
|
|
16
|
+
}
|
|
17
|
+
if (kept >= 0 && kept < count && !at.includes(kept)) {
|
|
18
|
+
kept < first ? at.unshift(kept) : at.push(kept);
|
|
19
|
+
}
|
|
20
|
+
return at;
|
|
21
|
+
};
|
|
22
|
+
export const drawOptions = (box, tag, rows, at, disabled) => {
|
|
23
|
+
const drawn = [...box.children];
|
|
24
|
+
while (drawn.length > at.length) {
|
|
25
|
+
drawn.pop()?.remove();
|
|
26
|
+
}
|
|
27
|
+
while (drawn.length < at.length) {
|
|
28
|
+
drawn.push(box.appendChild(box.ownerDocument.createElement(tag)));
|
|
29
|
+
}
|
|
30
|
+
at.forEach((index, i) => {
|
|
31
|
+
const row = rows[index];
|
|
32
|
+
const element = drawn[i];
|
|
33
|
+
Object.assign(element, {
|
|
34
|
+
active: row.active,
|
|
35
|
+
disabled: row.disabled || disabled,
|
|
36
|
+
selected: row.selected,
|
|
37
|
+
value: row.value,
|
|
38
|
+
});
|
|
39
|
+
if (element.textContent !== row.text) {
|
|
40
|
+
element.textContent = row.text;
|
|
41
|
+
}
|
|
42
|
+
element.setAttribute('aria-posinset', String(index + 1));
|
|
43
|
+
element.setAttribute('aria-setsize', String(rows.length));
|
|
44
|
+
});
|
|
45
|
+
return drawn;
|
|
46
|
+
};
|
|
@@ -26,7 +26,8 @@ export interface SegmentOptions {
|
|
|
26
26
|
* between them, read off `Intl`'s own rendering of a sample.
|
|
27
27
|
*
|
|
28
28
|
* `en-US` is month, day, year with slashes; `de-DE` day, month, year with
|
|
29
|
-
* dots; `ja-JP` year, month, day
|
|
29
|
+
* dots; `ja-JP` year, month, day; the `iso8601` calendar year, month, day
|
|
30
|
+
* with hyphens. A time follows the clock: a twelve hour
|
|
30
31
|
* locale ends with the half of the day.
|
|
31
32
|
*/
|
|
32
33
|
export declare const segmentLayout: (kind: SegmentedKind, locale: string, options?: SegmentOptions) => SegmentSlot[];
|
|
@@ -14,6 +14,16 @@ export const segmentLayout = (kind, locale, options = {}) => {
|
|
|
14
14
|
...(options.seconds ? { second: '2-digit' } : {}),
|
|
15
15
|
...(options.hourCycle ? { hourCycle: options.hourCycle } : {}),
|
|
16
16
|
});
|
|
17
|
+
if (kind === 'date' &&
|
|
18
|
+
(/-ca-iso8601/i.test(locale) || format.resolvedOptions().calendar === 'iso8601')) {
|
|
19
|
+
return [
|
|
20
|
+
{ kind: 'year' },
|
|
21
|
+
{ literal: '-' },
|
|
22
|
+
{ kind: 'month' },
|
|
23
|
+
{ literal: '-' },
|
|
24
|
+
{ kind: 'day' },
|
|
25
|
+
];
|
|
26
|
+
}
|
|
17
27
|
const known = new Set(['day', 'dayPeriod', 'hour', 'minute', 'month', 'second', 'year']);
|
|
18
28
|
const slots = [];
|
|
19
29
|
for (const part of format.formatToParts(SAMPLE)) {
|
|
@@ -44,9 +44,14 @@ export interface TooltipAt {
|
|
|
44
44
|
/**
|
|
45
45
|
* What the keyboard is reading, which `drawTooltip` says aloud and marks.
|
|
46
46
|
*
|
|
47
|
-
* The chart's `pointer` is one, and is what every chart passes.
|
|
47
|
+
* The chart's `pointer` is one, and is what every chart passes. A chart whose
|
|
48
|
+
* readings can be chosen passes one with `state` as well, a word such as
|
|
49
|
+
* "selected" said after where the reading stands.
|
|
48
50
|
*/
|
|
49
|
-
export type ReadingCursor = Pick<ChartPointerController, 'column' | 'keyed'> & Partial<Pick<ChartPointerController, 'position' | 'shows'
|
|
51
|
+
export type ReadingCursor = Pick<ChartPointerController, 'column' | 'keyed'> & Partial<Pick<ChartPointerController, 'position' | 'shows'>> & {
|
|
52
|
+
/** A word said after the reading's position, such as "selected", or nothing. */
|
|
53
|
+
state?: string;
|
|
54
|
+
};
|
|
50
55
|
/**
|
|
51
56
|
* What a pointer resting on a reading shows, as `Tooltip` pointed at a mark.
|
|
52
57
|
*
|
|
@@ -11,7 +11,7 @@ export const drawTooltip = (rows, at, tag, where, slotOf, reading) => {
|
|
|
11
11
|
const live = reading === undefined || rows.length === 0
|
|
12
12
|
? nothing
|
|
13
13
|
: html `<span aria-live="polite" part="live" role="status">${reading.keyed && row
|
|
14
|
-
? spokenReading(row, reading.column, (cell) => reading.shows?.(cell) !== false, reading.position)
|
|
14
|
+
? spokenReading(row, reading.column, (cell) => reading.shows?.(cell) !== false, reading.position, reading.state)
|
|
15
15
|
: ''}</span>`;
|
|
16
16
|
if (!row || rows.length === 0) {
|
|
17
17
|
return live;
|
|
@@ -9,6 +9,7 @@ import type { Row } from '../read-series/read-series.js';
|
|
|
9
9
|
* false for, which is a series a legend hid.
|
|
10
10
|
*
|
|
11
11
|
* `position` follows in brackets, such as "2 of 12", where the chart gives
|
|
12
|
-
* one: last, so the reading is heard before where it stands.
|
|
12
|
+
* one: last, so the reading is heard before where it stands. `state`, a word
|
|
13
|
+
* such as "selected", follows it in the same brackets, "2 of 12, selected".
|
|
13
14
|
*/
|
|
14
|
-
export declare const spokenReading: (row: Row, column?: number, shows?: (cell: number) => boolean, position?: string) => string;
|
|
15
|
+
export declare const spokenReading: (row: Row, column?: number, shows?: (cell: number) => boolean, position?: string, state?: string) => string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export const spokenReading = (row, column, shows = () => true, position = '') => {
|
|
2
|
-
const
|
|
1
|
+
export const spokenReading = (row, column, shows = () => true, position = '', state = '') => {
|
|
2
|
+
const bracket = [position, state].filter((word) => word !== '').join(', ');
|
|
3
|
+
const after = bracket === '' ? '' : ` (${bracket})`;
|
|
3
4
|
const cell = column === undefined ? undefined : row.cells[column];
|
|
4
5
|
if (cell) {
|
|
5
6
|
return `${`${row.label}, ${cell.name} ${cell.text}`.trim()}${after}`;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An `exportparts` list that forwards each of an inner component's parts under
|
|
3
|
+
* a prefix: `exportParts(['button', 'text'], 'copy')` is
|
|
4
|
+
* `button: copy-button, text: copy-text`.
|
|
5
|
+
*
|
|
6
|
+
* **One list per inner component, written once beside it**, so the next part
|
|
7
|
+
* Button gains reaches every family that renders a Button. Ten lists written
|
|
8
|
+
* out by hand each had to be found and edited when Button gained `spinner`.
|
|
9
|
+
*
|
|
10
|
+
* The inner component's parts are its own declared list, never ones read from
|
|
11
|
+
* a rendered element: the attribute is written on the server too, before
|
|
12
|
+
* anything has rendered.
|
|
13
|
+
*/
|
|
14
|
+
export declare const exportParts: (parts: readonly string[], prefix: string) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const exportParts = (parts, prefix) => parts.map((part) => `${part}: ${prefix}-${part}`).join(', ');
|
|
@@ -11,3 +11,12 @@
|
|
|
11
11
|
* shows up on the page as the mistake it is.
|
|
12
12
|
*/
|
|
13
13
|
export declare const fillTemplate: (template: string, values: Record<string, string | number>) => string;
|
|
14
|
+
/**
|
|
15
|
+
* A name made from a template and the words of what it acts on: `{key}` in
|
|
16
|
+
* the template filled with the words, or the words after the template where it
|
|
17
|
+
* has no `{key}`.
|
|
18
|
+
*
|
|
19
|
+
* The shape of every "Remove {label}" a page can override: written either way,
|
|
20
|
+
* "Remove" alone still names which one, as "Remove Draft".
|
|
21
|
+
*/
|
|
22
|
+
export declare const labelWith: (template: string, key: string, words: string) => string;
|
|
@@ -1 +1,4 @@
|
|
|
1
1
|
export const fillTemplate = (template, values) => Object.entries(values).reduce((out, [key, value]) => out.replaceAll(`{${key}}`, () => String(value)), template);
|
|
2
|
+
export const labelWith = (template, key, words) => template.includes(`{${key}}`)
|
|
3
|
+
? fillTemplate(template, { [key]: words })
|
|
4
|
+
: `${template} ${words}`.trim();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** What the marks are called in `CSS.highlights`, and in `base.css`. */
|
|
2
|
+
export declare const FILTER_MARK = "helix-filter-match";
|
|
3
|
+
/**
|
|
4
|
+
* Where a needle sits inside an element, as a range over its text nodes, or
|
|
5
|
+
* nothing where it does not.
|
|
6
|
+
*
|
|
7
|
+
* Walked rather than measured off `textContent`, because the words may be split
|
|
8
|
+
* across several nodes and wrapped in elements of the consumer's, and a match
|
|
9
|
+
* can begin in one node and end in another. The offsets are mapped back onto
|
|
10
|
+
* the nodes they came from, so the range covers the real text either way.
|
|
11
|
+
* Matched without regard to case, as a filter matches.
|
|
12
|
+
*/
|
|
13
|
+
export declare const rangeOf: (root: Element, needle: string) => Range[];
|
|
14
|
+
/**
|
|
15
|
+
* Marks where `needle` matches in each of `elements`, for `owner`, without
|
|
16
|
+
* touching the elements. An empty needle takes the owner's marks away, which
|
|
17
|
+
* is also what an owner leaving the page calls.
|
|
18
|
+
*
|
|
19
|
+
* **The Custom Highlight API paints a range, and nothing is inserted.** The
|
|
20
|
+
* words are usually the consumer's own light DOM, slotted through as many as
|
|
21
|
+
* two shadow roots, and wrapping the matched part in a `<mark>` would mean
|
|
22
|
+
* rewriting their content: markup they wrote would come back changed. So an
|
|
23
|
+
* option holding a link or an icon is marked as readily as one holding a bare
|
|
24
|
+
* word. Listbox marks what its filter matched this way, and so every family
|
|
25
|
+
* that filters through one: Select, Combobox, Command and Time Picker.
|
|
26
|
+
*
|
|
27
|
+
* **The rule that paints it lives in `base.css`**, as
|
|
28
|
+
* `::highlight(helix-filter-match)`, rather than in a component's styles. A
|
|
29
|
+
* highlight is styled from the tree its text lives in, and that text lives in
|
|
30
|
+
* the consumer's document.
|
|
31
|
+
*
|
|
32
|
+
* Nothing at all where the API is missing, as on a server, in a test
|
|
33
|
+
* environment or in an older browser: the text nodes are not walked for an
|
|
34
|
+
* answer nothing can paint.
|
|
35
|
+
*/
|
|
36
|
+
export declare const markMatches: (owner: Element, elements: Iterable<Element>, needle: string) => void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const FILTER_MARK = 'helix-filter-match';
|
|
2
|
+
const matched = new Map();
|
|
3
|
+
export const rangeOf = (root, needle) => {
|
|
4
|
+
const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
5
|
+
const nodes = [];
|
|
6
|
+
let whole = '';
|
|
7
|
+
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
|
|
8
|
+
nodes.push([node, whole.length]);
|
|
9
|
+
whole += node.nodeValue;
|
|
10
|
+
}
|
|
11
|
+
const at = whole.toLowerCase().indexOf(needle.toLowerCase());
|
|
12
|
+
if (at < 0 || needle === '') {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const range = root.ownerDocument.createRange();
|
|
16
|
+
const [start, before] = nodes.findLast(([, from]) => from <= at);
|
|
17
|
+
range.setStart(start, at - before);
|
|
18
|
+
const end = at + needle.length;
|
|
19
|
+
const [last, under] = nodes.findLast(([, from]) => from < end);
|
|
20
|
+
range.setEnd(last, end - under);
|
|
21
|
+
return [range];
|
|
22
|
+
};
|
|
23
|
+
export const markMatches = (owner, elements, needle) => {
|
|
24
|
+
const registry = globalThis.CSS?.highlights;
|
|
25
|
+
if (registry === undefined) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (needle === '') {
|
|
29
|
+
matched.delete(owner);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
matched.set(owner, [...elements].flatMap((element) => rangeOf(element, needle)));
|
|
33
|
+
}
|
|
34
|
+
const all = [...matched.values()].flat();
|
|
35
|
+
if (all.length === 0) {
|
|
36
|
+
registry.delete(FILTER_MARK);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
registry.set(FILTER_MARK, new Highlight(...all));
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The element focus lands on when a trigger is focused: the trigger itself,
|
|
3
|
+
* or, for a component that delegates focus, the control inside its shadow root,
|
|
4
|
+
* followed down through any component nested there.
|
|
5
|
+
*
|
|
6
|
+
* **What a description or a name has to be written on**, since a reader hears
|
|
7
|
+
* the element that has focus: a tooltip describing a Helix button describes
|
|
8
|
+
* the `<button>` inside it, and a badge counting on an icon button counts on
|
|
9
|
+
* the same one. Read from outside through `shadowRoot`, so it works for any
|
|
10
|
+
* component with an open root and names none.
|
|
11
|
+
*/
|
|
12
|
+
export declare const focusTarget: (trigger: Element) => Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const CONTROL = 'a[href], button, input, select, textarea, [contenteditable], [tabindex]:not([tabindex="-1"])';
|
|
2
|
+
export const focusTarget = (trigger) => {
|
|
3
|
+
const root = trigger.shadowRoot;
|
|
4
|
+
if (!root?.delegatesFocus) {
|
|
5
|
+
return trigger;
|
|
6
|
+
}
|
|
7
|
+
const inner = root.querySelector(CONTROL);
|
|
8
|
+
return inner ? focusTarget(inner) : trigger;
|
|
9
|
+
};
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What takes a press of its own inside a row or an item that is pressable as a
|
|
3
|
+
* whole: a link, a control, a label, a disclosure's summary, or anything a page
|
|
4
|
+
* gave a `tabindex`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PRESSABLE = "a[href], button, input, label, select, summary, textarea, [tabindex]";
|
|
7
|
+
/**
|
|
8
|
+
* Whether a press landed on a control of the item's own, before `boundary` in
|
|
9
|
+
* its path, so the item leaves the press to that control.
|
|
10
|
+
*
|
|
11
|
+
* **The composed path, so a control inside a component counts**: the `<button>`
|
|
12
|
+
* in a slotted Helix button's shadow root is on the path and matches, where the
|
|
13
|
+
* event's retargeted `target` is only the host. The boundary itself and
|
|
14
|
+
* everything past it is not asked, so a row that is a `[tabindex]` stop does
|
|
15
|
+
* not count as its own control.
|
|
16
|
+
*/
|
|
17
|
+
export declare const pressedOwnControl: (event: Event, boundary: EventTarget, pressable?: string) => boolean;
|
|
1
18
|
/**
|
|
2
19
|
* Everything inside `root` the reader can reach with Tab, in document order.
|
|
3
20
|
*
|
|
@@ -7,6 +7,14 @@ const NATIVE_FOCUSABLE = [
|
|
|
7
7
|
'[contenteditable]:not([contenteditable="false"])',
|
|
8
8
|
'[tabindex]:not([tabindex="-1"])',
|
|
9
9
|
].join(',');
|
|
10
|
+
export const PRESSABLE = 'a[href], button, input, label, select, summary, textarea, [tabindex]';
|
|
11
|
+
export const pressedOwnControl = (event, boundary, pressable = PRESSABLE) => {
|
|
12
|
+
const path = event.composedPath();
|
|
13
|
+
const end = path.indexOf(boundary);
|
|
14
|
+
return path
|
|
15
|
+
.slice(0, end < 0 ? 0 : end)
|
|
16
|
+
.some((node) => node instanceof Element && node.matches(pressable));
|
|
17
|
+
};
|
|
10
18
|
export const focusableElements = (root) => [...root.querySelectorAll('*')].filter((node) => (node.matches(NATIVE_FOCUSABLE) || node.shadowRoot?.delegatesFocus === true) &&
|
|
11
19
|
node.getClientRects().length > 0);
|
|
12
20
|
export const deepActiveElement = () => {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a key moves focus in a table read as a grid, as arithmetic over places.
|
|
3
|
+
*
|
|
4
|
+
* Pure, so a spec holds the keyboard map without a page, and in core because
|
|
5
|
+
* Table's `grid` and Data Table both read it: each finds its cells and focuses
|
|
6
|
+
* them, and this says which place a key reaches from which.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A cell's place in the grid. The row of headings is row `-1`, the rows count
|
|
10
|
+
* from 0 in the order they are drawn, and a `selectable` table's checkbox
|
|
11
|
+
* column is column 0.
|
|
12
|
+
*/
|
|
13
|
+
export interface GridPlace {
|
|
14
|
+
column: number;
|
|
15
|
+
row: number;
|
|
16
|
+
}
|
|
17
|
+
/** The edges of the grid a key cannot move past. */
|
|
18
|
+
export interface GridBounds {
|
|
19
|
+
/** The last row, counted as `GridPlace.row` counts. */
|
|
20
|
+
last: number;
|
|
21
|
+
/** The first row: `-1` where there is a row of headings, and `0` where there is not. */
|
|
22
|
+
top: number;
|
|
23
|
+
/** How many columns the grid has. */
|
|
24
|
+
width: number;
|
|
25
|
+
}
|
|
26
|
+
/** What a key reads besides itself. */
|
|
27
|
+
export interface GridKeys {
|
|
28
|
+
/** Whether Control or Command was held, which takes Home and End to the corners. */
|
|
29
|
+
control: boolean;
|
|
30
|
+
/** How many rows Page Up and Page Down move. */
|
|
31
|
+
page: number;
|
|
32
|
+
/**
|
|
33
|
+
* Which way a horizontal arrow moves along the line: `1` forward, `-1` back,
|
|
34
|
+
* as core `inlineStep` answers, so a right-to-left page is already flipped.
|
|
35
|
+
*/
|
|
36
|
+
step: -1 | 0 | 1;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The place a key reaches from `from`, or `undefined` for a key that does not
|
|
40
|
+
* move focus. A key at an edge answers the place it is on, so the caller still
|
|
41
|
+
* takes the key.
|
|
42
|
+
*
|
|
43
|
+
* | Key | Place |
|
|
44
|
+
* |---|---|
|
|
45
|
+
* | Up, Down | One row, the headings included. |
|
|
46
|
+
* | Left, Right | One column along the line, as `step` says. |
|
|
47
|
+
* | Home, End | The first or the last column of the row. |
|
|
48
|
+
* | Control with Home or End | The first column of the first row, or the last of the last. |
|
|
49
|
+
* | Page Up, Page Down | `page` rows. |
|
|
50
|
+
*/
|
|
51
|
+
export declare const gridMove: (key: string, from: GridPlace, bounds: GridBounds, keys: GridKeys) => GridPlace | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* How many rows Page Up and Page Down move: the rows of `tall` pixels that fit
|
|
54
|
+
* in `room`, less one, so the row focus left stays in view. At least one.
|
|
55
|
+
*/
|
|
56
|
+
export declare const pageRows: (room: number, tall: number) => number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const within = (value, low, high) => Math.max(low, Math.min(high, value));
|
|
2
|
+
export const gridMove = (key, from, bounds, keys) => {
|
|
3
|
+
const { last, top, width } = bounds;
|
|
4
|
+
const edge = Math.max(0, width - 1);
|
|
5
|
+
const row = (to) => ({ column: from.column, row: within(to, top, last) });
|
|
6
|
+
switch (key) {
|
|
7
|
+
case 'ArrowUp':
|
|
8
|
+
return row(from.row - 1);
|
|
9
|
+
case 'ArrowDown':
|
|
10
|
+
return row(from.row + 1);
|
|
11
|
+
case 'ArrowLeft':
|
|
12
|
+
case 'ArrowRight':
|
|
13
|
+
return keys.step === 0
|
|
14
|
+
? undefined
|
|
15
|
+
: { column: within(from.column + keys.step, 0, edge), row: from.row };
|
|
16
|
+
case 'Home':
|
|
17
|
+
return { column: 0, row: keys.control ? top : from.row };
|
|
18
|
+
case 'End':
|
|
19
|
+
return { column: edge, row: keys.control ? last : from.row };
|
|
20
|
+
case 'PageUp':
|
|
21
|
+
return row(from.row - keys.page);
|
|
22
|
+
case 'PageDown':
|
|
23
|
+
return row(from.row + keys.page);
|
|
24
|
+
default:
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export const pageRows = (room, tall) => tall > 0 ? Math.max(1, Math.floor(room / tall) - 1) : 1;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime's own locale: in a browser, the language its interface is set
|
|
3
|
+
* to, which is what every `Intl` call given no locale uses.
|
|
4
|
+
*/
|
|
5
|
+
export declare const runtimeLocale: () => string;
|
|
1
6
|
/**
|
|
2
7
|
* The locale an element speaks: `own`, the element's own `locale` property,
|
|
3
8
|
* where one is set, and otherwise the `lang` of the nearest element with one,
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
let runtime;
|
|
2
|
+
export const runtimeLocale = () => {
|
|
3
|
+
runtime ??= new Intl.Locale(new Intl.DateTimeFormat().resolvedOptions().locale);
|
|
4
|
+
return runtime.toString();
|
|
5
|
+
};
|
|
2
6
|
const usable = (lang) => {
|
|
3
7
|
try {
|
|
4
8
|
const asked = new Intl.Locale(lang);
|
|
5
|
-
|
|
6
|
-
return asked.region === undefined && asked.language === runtime
|
|
9
|
+
runtimeLocale();
|
|
10
|
+
return asked.region === undefined && asked.language === runtime?.language
|
|
11
|
+
? ''
|
|
12
|
+
: asked.toString();
|
|
7
13
|
}
|
|
8
14
|
catch {
|
|
9
15
|
return '';
|
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
/** Writes a number in a locale with `Intl.NumberFormat` options. */
|
|
11
11
|
export declare const formatNumber: (value: number, locale?: string, options?: Intl.NumberFormatOptions) => string;
|
|
12
|
+
/**
|
|
13
|
+
* A share, from 0 to 1, as a locale writes a whole percentage: `40%`, or
|
|
14
|
+
* `40 %` in French. For `aria-valuetext`, since a bare `aria-valuenow` is read
|
|
15
|
+
* as a number with no unit. Splitter's position and Meter's and Progress's
|
|
16
|
+
* readings are written with it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const formatPercent: (share: number, locale?: string) => string;
|
|
12
19
|
/**
|
|
13
20
|
* A reading in words: `value` written with `options` where they say another
|
|
14
21
|
* style, and otherwise `share`, from 0 to 1, as a percentage, whole unless
|
|
@@ -10,6 +10,7 @@ const formatter = (locale, options) => {
|
|
|
10
10
|
};
|
|
11
11
|
export const formatNumber = (value, locale = '', options) => formatter(locale, options).format(value);
|
|
12
12
|
const WHOLE_PERCENT = { maximumFractionDigits: 0, style: 'percent' };
|
|
13
|
+
export const formatPercent = (share, locale = '') => formatNumber(share, locale, WHOLE_PERCENT);
|
|
13
14
|
export const readingText = (value, share, locale = '', options) => options && options.style !== 'percent'
|
|
14
15
|
? formatNumber(value, locale, options)
|
|
15
16
|
: formatNumber(share, locale, options ?? WHOLE_PERCENT);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The copy of an item a pointer drags: what the item draws, through every
|
|
3
|
+
* shadow root and slot in it, rebuilt as plain elements with the styles the
|
|
4
|
+
* browser computed for each written on it.
|
|
5
|
+
*
|
|
6
|
+
* `ReorderController` puts the copy in the host's own shadow root, in the top
|
|
7
|
+
* layer, so the page's markup is never touched and no scrolling box clips it.
|
|
8
|
+
* A copy is inert: it has no custom elements, no slots, and no attributes but
|
|
9
|
+
* a picture's, so nothing in it upgrades, takes the host's children, or joins
|
|
10
|
+
* a radio group. Pseudo-elements are not copied.
|
|
11
|
+
*/
|
|
12
|
+
import { nothing, type TemplateResult } from 'lit';
|
|
13
|
+
/**
|
|
14
|
+
* The part a host draws for the drag preview: an empty manual popover,
|
|
15
|
+
* hidden from assistive technology, which `ReorderController` fills with a
|
|
16
|
+
* copy of the item a pointer drags and shows in the top layer. Closed, it
|
|
17
|
+
* takes no room. Drawn while `on`, which is while the host reorders.
|
|
18
|
+
*/
|
|
19
|
+
export declare const drawDragPreview: (on: boolean) => TemplateResult | typeof nothing;
|
|
20
|
+
/** A point in the viewport, where the boxes of a copy are measured from. */
|
|
21
|
+
interface Origin {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The box an element draws: its own, or the boxes of what it draws together,
|
|
27
|
+
* for an element that is `display: contents`, such as a row of a grid.
|
|
28
|
+
*/
|
|
29
|
+
export declare const boxOf: (element: Element) => DOMRect;
|
|
30
|
+
/**
|
|
31
|
+
* A copy of what a node draws, for a drag preview, with every style the
|
|
32
|
+
* browser computed for each part written on it. The custom properties are
|
|
33
|
+
* left out, having done their work, and so are the logical properties, whose
|
|
34
|
+
* physical twins are written: two of a pair in one style do not agree on
|
|
35
|
+
* which wins.
|
|
36
|
+
*
|
|
37
|
+
* A `placed` node is put where it is with `position: absolute`, measured from
|
|
38
|
+
* `origin`, the padding box it is placed in, rather than laid out again. The
|
|
39
|
+
* root of a preview is placed in the preview, whose padding box is the root's
|
|
40
|
+
* box as `boxOf` measures it, and so is whatever a grid lays out, since a
|
|
41
|
+
* subgrid has no tracks of its own once it is copied out of its grid. An
|
|
42
|
+
* element that is `display: contents`, such as a row whose cells sit in the
|
|
43
|
+
* grid of its table, has each of its cells put where it is.
|
|
44
|
+
*/
|
|
45
|
+
export declare const dragPreviewOf: (node: Node, placed: boolean, origin: Origin) => Node[];
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { html, nothing } from 'lit';
|
|
2
|
+
export const drawDragPreview = (on) => on ? html `<div part="drag-preview" popover="manual" aria-hidden="true" inert></div>` : nothing;
|
|
3
|
+
const drawn = (node) => {
|
|
4
|
+
const assigned = node instanceof HTMLSlotElement ? node.assignedNodes({ flatten: true }) : [];
|
|
5
|
+
return [...(assigned.length ? assigned : (node.shadowRoot ?? node).childNodes)];
|
|
6
|
+
};
|
|
7
|
+
export const boxOf = (element) => {
|
|
8
|
+
if (getComputedStyle(element).display !== 'contents') {
|
|
9
|
+
return element.getBoundingClientRect();
|
|
10
|
+
}
|
|
11
|
+
let [left, top, right, bottom] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
12
|
+
for (const node of drawn(element)) {
|
|
13
|
+
const box = node instanceof Element && boxOf(node);
|
|
14
|
+
if (box && (box.width || box.height)) {
|
|
15
|
+
left = Math.min(left, box.left);
|
|
16
|
+
top = Math.min(top, box.top);
|
|
17
|
+
right = Math.max(right, box.right);
|
|
18
|
+
bottom = Math.max(bottom, box.bottom);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return left <= right ? new DOMRect(left, top, right - left, bottom - top) : new DOMRect();
|
|
22
|
+
};
|
|
23
|
+
export const dragPreviewOf = (node, placed, origin) => {
|
|
24
|
+
if (node instanceof Text) {
|
|
25
|
+
return [node.cloneNode()];
|
|
26
|
+
}
|
|
27
|
+
const style = node instanceof Element ? getComputedStyle(node) : undefined;
|
|
28
|
+
if (!style || style.display === 'none') {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const element = node;
|
|
32
|
+
const tag = element.localName;
|
|
33
|
+
const whole = /^(img|svg)$/.test(tag);
|
|
34
|
+
const copy = whole
|
|
35
|
+
? element.cloneNode(true)
|
|
36
|
+
: document.createElement(/-|^slot$/.test(tag) ? 'div' : tag);
|
|
37
|
+
const contents = style.display === 'contents';
|
|
38
|
+
const box = element.getBoundingClientRect();
|
|
39
|
+
let written = '';
|
|
40
|
+
for (let index = 0; index < style.length; index += 1) {
|
|
41
|
+
const name = style.item(index);
|
|
42
|
+
if (!/^--|block|inline|start-|end-/.test(name)) {
|
|
43
|
+
written += `${name}:${style.getPropertyValue(name)};`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
copy.style.cssText =
|
|
47
|
+
placed && !contents
|
|
48
|
+
? `${written}position:absolute;inset:auto;left:${box.left - origin.x}px;top:${box.top - origin.y}px;width:${box.width}px;height:${box.height}px;margin:0;box-sizing:border-box;grid-area:auto`
|
|
49
|
+
: written;
|
|
50
|
+
if (!whole) {
|
|
51
|
+
const inner = !contents && (placed || style.position !== 'static')
|
|
52
|
+
? { x: box.left + element.clientLeft, y: box.top + element.clientTop }
|
|
53
|
+
: origin;
|
|
54
|
+
const grid = contents ? placed : style.display.endsWith('grid');
|
|
55
|
+
copy.append(...drawn(element).flatMap((child) => dragPreviewOf(child, grid, inner)));
|
|
56
|
+
}
|
|
57
|
+
return [copy];
|
|
58
|
+
};
|