@fusion.dev/helix-core 0.146.0 → 0.148.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 +3129 -282
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +5 -3
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +6 -0
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +13 -0
- package/dist/controllers/children-controller/children-controller.d.ts +28 -0
- package/dist/controllers/children-controller/children-controller.js +18 -0
- package/dist/controllers/copy-controller/copy-controller.d.ts +4 -1
- package/dist/controllers/copy-controller/copy-controller.js +2 -1
- package/dist/controllers/countdown-controller/countdown-controller.d.ts +19 -2
- package/dist/controllers/countdown-controller/countdown-controller.js +30 -3
- 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 +30 -0
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +8 -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/source-controller/source-controller.js +2 -0
- package/dist/controllers/swipe-controller/swipe-controller.d.ts +10 -1
- package/dist/controllers/swipe-controller/swipe-controller.js +1 -1
- package/dist/controllers/virtualize-controller/virtualize-controller.js +8 -6
- package/dist/events/band.d.ts +13 -0
- package/dist/events/band.js +11 -0
- package/dist/events/details.d.ts +5 -0
- package/dist/events/events.d.ts +4 -2
- package/dist/events/events.js +3 -1
- package/dist/events/names.d.ts +3 -0
- package/dist/events/names.js +13 -88
- package/dist/helix-element/helix-element.d.ts +14 -5
- package/dist/helix-element/helix-element.js +13 -45
- package/dist/helix-element/react-bridge.d.ts +13 -0
- package/dist/helix-element/react-bridge.js +1 -1
- package/dist/index.d.ts +40 -17
- package/dist/index.js +38 -15
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +12 -0
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +4 -0
- package/dist/mixins/mixin-dismissible/mixin-dismissible.js +25 -4
- package/dist/mixins/mixin-picker-feedback/mixin-picker-feedback.d.ts +42 -0
- package/dist/mixins/mixin-picker-feedback/mixin-picker-feedback.js +46 -0
- package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +2 -11
- package/dist/mixins/mixin-picker-field/mixin-picker-field.js +1 -6
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +1 -1
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +10 -6
- package/dist/mixins/mixin-viewport/mixin-viewport.js +8 -7
- package/dist/utils/assign-slots/assign-slots.d.ts +18 -0
- package/dist/utils/assign-slots/assign-slots.js +14 -0
- package/dist/utils/audit-name/audit-name.d.ts +7 -2
- package/dist/utils/audit-name/audit-name.js +12 -3
- package/dist/utils/audit-source/audit-source-off.d.ts +10 -0
- package/dist/utils/audit-source/audit-source-off.js +1 -0
- package/dist/utils/audit-source/audit-source.d.ts +19 -0
- package/dist/utils/audit-source/audit-source.js +27 -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/call-listener/call-listener.d.ts +16 -0
- package/dist/utils/call-listener/call-listener.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/chord.d.ts +14 -0
- package/dist/utils/chord/chord.js +1 -0
- package/dist/utils/chord/key-names.d.ts +30 -0
- package/dist/utils/chord/key-names.js +52 -0
- package/dist/utils/clamp/clamp.d.ts +7 -0
- package/dist/utils/clamp/clamp.js +1 -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/css-color/css-color.js +1 -1
- package/dist/utils/data-options/data-options.d.ts +60 -0
- package/dist/utils/data-options/data-options.js +46 -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/elapsed-time/elapsed-time.d.ts +1 -1
- package/dist/utils/elapsed-time/elapsed-time.js +2 -5
- 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/host-words/host-words.d.ts +16 -0
- package/dist/utils/host-words/host-words.js +13 -0
- package/dist/utils/load-more/load-more.d.ts +25 -0
- package/dist/utils/load-more/load-more.js +1 -0
- package/dist/utils/locale-of/locale-of.d.ts +18 -0
- package/dist/utils/locale-of/locale-of.js +26 -0
- package/dist/utils/nearest/nearest.d.ts +9 -0
- package/dist/utils/nearest/nearest.js +9 -0
- package/dist/utils/number-format/number-format.d.ts +16 -0
- package/dist/utils/number-format/number-format.js +5 -0
- package/dist/utils/own-rule/own-rule.d.ts +16 -0
- package/dist/utils/own-rule/own-rule.js +7 -0
- package/dist/utils/platform/platform.d.ts +8 -0
- package/dist/utils/platform/platform.js +1 -0
- package/dist/utils/read-series/read-series.d.ts +14 -0
- package/dist/utils/read-series/read-series.js +15 -0
- package/dist/utils/relative-time/relative-time.d.ts +8 -0
- package/dist/utils/relative-time/relative-time.js +5 -2
- package/dist/utils/report-render/report-render.js +2 -14
- package/dist/utils/report-runtime/report-runtime.d.ts +11 -1
- package/dist/utils/report-runtime/report-runtime.js +2 -14
- package/dist/utils/report-usage/report-usage.js +2 -14
- 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 +50 -0
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +14 -3
- 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-glyph/slotted-glyph.d.ts +33 -0
- package/dist/utils/slotted-glyph/slotted-glyph.js +27 -0
- package/dist/utils/toolbar-around/toolbar-around.d.ts +18 -0
- package/dist/utils/toolbar-around/toolbar-around.js +5 -0
- package/dist/utils/typeahead/typeahead.d.ts +8 -0
- package/dist/utils/typeahead/typeahead.js +1 -0
- package/dist/utils/verdict-flags/verdict-flags.d.ts +13 -0
- package/dist/utils/verdict-flags/verdict-flags.js +6 -0
- package/dist/utils/written-list/written-list.d.ts +31 -0
- package/dist/utils/written-list/written-list.js +16 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +15 -4
|
@@ -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;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words that name a host, for a part inside its shadow root that must
|
|
3
|
+
* carry the same name: the list a Select or a Time Picker opens.
|
|
4
|
+
*
|
|
5
|
+
* **Words rather than a reference**, because the part is inside the host's
|
|
6
|
+
* shadow root, and neither an IDREF on the page nor the host's labels reach
|
|
7
|
+
* it. Read in the order a browser names the host: its `aria-labelledby`, its
|
|
8
|
+
* `aria-label`, then `own`, the name the component draws itself (its `label`),
|
|
9
|
+
* then a `<label>` for it. Each element's words are read as a name reads
|
|
10
|
+
* them, so hidden text is left out and runs of white space are one space.
|
|
11
|
+
*
|
|
12
|
+
* Empty where nothing names the host, which leaves the fallback to the caller.
|
|
13
|
+
*/
|
|
14
|
+
export declare const hostWords: (host: HTMLElement & {
|
|
15
|
+
labels?: NodeList | null;
|
|
16
|
+
}, own?: string) => string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { elementById } from '../element-by-id/element-by-id.js';
|
|
2
|
+
import { nameText } from '../name-text/name-text.js';
|
|
3
|
+
const wordsOf = (named) => named
|
|
4
|
+
.map((one) => (one ? nameText(one).replace(/\s+/g, ' ').trim() : ''))
|
|
5
|
+
.filter(Boolean)
|
|
6
|
+
.join(' ');
|
|
7
|
+
export const hostWords = (host, own = '') => {
|
|
8
|
+
const ids = (host.getAttribute('aria-labelledby') ?? '').split(/\s+/).filter(Boolean);
|
|
9
|
+
return (wordsOf(ids.map((id) => elementById(host, id))) ||
|
|
10
|
+
host.getAttribute('aria-label')?.trim() ||
|
|
11
|
+
own ||
|
|
12
|
+
wordsOf([...(host.labels ?? [])]));
|
|
13
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Where a scrolling set stands, for the question of whether to ask for more. */
|
|
2
|
+
export interface EndInSight {
|
|
3
|
+
/** The length the set last asked for more at, or `-1` where it never has. */
|
|
4
|
+
askedAt: number;
|
|
5
|
+
/** The set's length now, which the answer is kept for. */
|
|
6
|
+
count: number;
|
|
7
|
+
/** How far below the bottom of the box the set goes, in pixels. */
|
|
8
|
+
remaining: number;
|
|
9
|
+
/** The box's own height, in pixels: one screen. */
|
|
10
|
+
screen: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Whether a reader has come within one screen of the end of a set, and the
|
|
14
|
+
* set has not already asked for more at this length: React Aria's
|
|
15
|
+
* `onLoadMore` rule.
|
|
16
|
+
*
|
|
17
|
+
* **Once for each length**, so a scroll that goes on asks again only after the
|
|
18
|
+
* next page arrived and the length moved. False while the box has no height or
|
|
19
|
+
* the set no items, so a list that has not been laid out asks for nothing.
|
|
20
|
+
* The caller keeps `askedAt`, setting it to `count` when this answers true.
|
|
21
|
+
*
|
|
22
|
+
* Listbox asks this of its own scroll, and `VirtualizeController.endInSight`
|
|
23
|
+
* of the rows it lays out, so the two lists ask on the same terms.
|
|
24
|
+
*/
|
|
25
|
+
export declare const endInSight: ({ askedAt, count, remaining, screen }: EndInSight) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const endInSight = ({ askedAt, count, remaining, screen }) => count > 0 && screen > 0 && askedAt !== count && remaining <= screen;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The locale an element speaks: `own`, the element's own `locale` property,
|
|
3
|
+
* where one is set, and otherwise the `lang` of the nearest element with one,
|
|
4
|
+
* read out through any shadow root the element is drawn in.
|
|
5
|
+
*
|
|
6
|
+
* **The page's language is the default a reader expects.** A date picker on a
|
|
7
|
+
* page written `lang="de"` that names its months in English and starts its
|
|
8
|
+
* weeks on Sunday is a widget that ignored the one place the page said what
|
|
9
|
+
* language it is in; Shoelace and Web Awesome read the same attribute.
|
|
10
|
+
* `lang=""` is the page saying it does not know, and answers empty.
|
|
11
|
+
*
|
|
12
|
+
* Empty where nothing says, which every `Intl` call here takes as the
|
|
13
|
+
* runtime's own locale, and empty for a bare language the runtime already
|
|
14
|
+
* speaks (see `usable`). Read when asked rather than watched, so a page that
|
|
15
|
+
* changes its `lang` is answered in the new one on the element's next render.
|
|
16
|
+
* A server shim with no `closest` answers `own`, or empty.
|
|
17
|
+
*/
|
|
18
|
+
export declare const localeOf: (element: Element, own?: string) => string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
let runtime;
|
|
2
|
+
const usable = (lang) => {
|
|
3
|
+
try {
|
|
4
|
+
const asked = new Intl.Locale(lang);
|
|
5
|
+
runtime ??= new Intl.Locale(new Intl.DateTimeFormat().resolvedOptions().locale);
|
|
6
|
+
return asked.region === undefined && asked.language === runtime.language ? '' : asked.toString();
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export const localeOf = (element, own = '') => {
|
|
13
|
+
if (own) {
|
|
14
|
+
return own;
|
|
15
|
+
}
|
|
16
|
+
let from = element;
|
|
17
|
+
while (from && typeof from.closest === 'function') {
|
|
18
|
+
const found = from.closest('[lang]');
|
|
19
|
+
if (found) {
|
|
20
|
+
return usable(found.getAttribute('lang') ?? '');
|
|
21
|
+
}
|
|
22
|
+
const root = from.getRootNode?.();
|
|
23
|
+
from = root?.host ?? null;
|
|
24
|
+
}
|
|
25
|
+
return '';
|
|
26
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value in `values` nearest `target`, the first of two at the same
|
|
3
|
+
* distance, or `undefined` for none.
|
|
4
|
+
*
|
|
5
|
+
* Drawer's `snapTo` and Rating's reading of a score between its steps each
|
|
6
|
+
* wrote this loop. Given in ascending order, a target halfway between two
|
|
7
|
+
* goes to the lower, which is the rule both kept.
|
|
8
|
+
*/
|
|
9
|
+
export declare const nearest: (values: Iterable<number>, target: number) => number | undefined;
|
|
@@ -9,6 +9,22 @@
|
|
|
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;
|
|
19
|
+
/**
|
|
20
|
+
* A reading in words: `value` written with `options` where they say another
|
|
21
|
+
* style, and otherwise `share`, from 0 to 1, as a percentage, whole unless
|
|
22
|
+
* `options` say more.
|
|
23
|
+
*
|
|
24
|
+
* Meter and Progress each wrote this, with the value each holds to its range
|
|
25
|
+
* and the share of it that is filled.
|
|
26
|
+
*/
|
|
27
|
+
export declare const readingText: (value: number, share: number, locale?: string, options?: Intl.NumberFormatOptions) => string;
|
|
12
28
|
/**
|
|
13
29
|
* Reads a number written in a locale with the same options it is shown with,
|
|
14
30
|
* or `null` where the text is no number.
|
|
@@ -9,6 +9,11 @@ const formatter = (locale, options) => {
|
|
|
9
9
|
return found;
|
|
10
10
|
};
|
|
11
11
|
export const formatNumber = (value, locale = '', options) => formatter(locale, options).format(value);
|
|
12
|
+
const WHOLE_PERCENT = { maximumFractionDigits: 0, style: 'percent' };
|
|
13
|
+
export const formatPercent = (share, locale = '') => formatNumber(share, locale, WHOLE_PERCENT);
|
|
14
|
+
export const readingText = (value, share, locale = '', options) => options && options.style !== 'percent'
|
|
15
|
+
? formatNumber(value, locale, options)
|
|
16
|
+
: formatNumber(share, locale, options ?? WHOLE_PERCENT);
|
|
12
17
|
const grouped = (text, separator, decimal) => {
|
|
13
18
|
const whole = (text.split(decimal)[0] ?? '').replace(/^[+-]/u, '');
|
|
14
19
|
return new RegExp(`^\\d{1,3}(${literal(separator)}\\d{3})+$`, 'u').test(whole);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page's own rule for a field's value: a message, a list of them, or `true`,
|
|
3
|
+
* `null` or nothing to accept the value. Input's and Textarea's `validate`.
|
|
4
|
+
*/
|
|
5
|
+
export type OwnRule = (value: string) => string | string[] | true | null | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Applies a page's own rule after the native verdict was copied, as React
|
|
8
|
+
* Aria's TextField `validate` does: only while the browser finds nothing
|
|
9
|
+
* wrong, so a required empty field says the browser's sentence first. A list
|
|
10
|
+
* of messages is joined with spaces, and the message is a `customError`
|
|
11
|
+
* anchored at the control, so the bubble points at the text.
|
|
12
|
+
*
|
|
13
|
+
* A function rather than a member of `MixinField`, so only the fields that
|
|
14
|
+
* take `validate` carry it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const applyOwnRule: (internals: ElementInternals, control: HTMLInputElement | HTMLTextAreaElement, rule: OwnRule | undefined, value: string) => void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const applyOwnRule = (internals, control, rule, value) => {
|
|
2
|
+
const own = control.validity.valid ? rule?.(value) : undefined;
|
|
3
|
+
const message = Array.isArray(own) ? own.join(' ') : typeof own === 'string' ? own : '';
|
|
4
|
+
if (message !== '') {
|
|
5
|
+
internals.setValidity({ customError: true }, message, control);
|
|
6
|
+
}
|
|
7
|
+
};
|
|
@@ -21,3 +21,11 @@ export declare const appleTouch: (platform: Platform | undefined) => boolean;
|
|
|
21
21
|
* `mod` as the key it names.
|
|
22
22
|
*/
|
|
23
23
|
export declare const onApple: (platform?: Platform | undefined) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the browser runs on Android, where TalkBack reads no `aria-sort`.
|
|
26
|
+
*
|
|
27
|
+
* React Aria's table header describes its sort order in words there for the
|
|
28
|
+
* same reason, so a reader who cannot hear the state still hears which way a
|
|
29
|
+
* column is sorted. From the user agent; `false` on a server.
|
|
30
|
+
*/
|
|
31
|
+
export declare const onAndroid: (platform?: Platform | undefined) => boolean;
|
|
@@ -9,3 +9,4 @@ export const appleTouch = (platform) => {
|
|
|
9
9
|
return platform.platform === 'MacIntel' && (platform.maxTouchPoints ?? 0) > 1;
|
|
10
10
|
};
|
|
11
11
|
export const onApple = (platform = typeof navigator === 'undefined' ? undefined : navigator) => platform !== undefined && /mac|iphone|ipad|ipod/i.test(platform.userAgent ?? '');
|
|
12
|
+
export const onAndroid = (platform = typeof navigator === 'undefined' ? undefined : navigator) => platform !== undefined && /android/i.test(platform.userAgent ?? '');
|
|
@@ -136,3 +136,17 @@ export declare const readRows: (table: Element) => Row[];
|
|
|
136
136
|
* name is written, in `lines`, `hidden-series` and `hide()` alike.
|
|
137
137
|
*/
|
|
138
138
|
export declare const seriesKey: (name: string) => string;
|
|
139
|
+
/** Where the highest and the lowest readings are, by place, or none. */
|
|
140
|
+
export interface Extremes {
|
|
141
|
+
/** The place of the highest reading, the first where several tie. */
|
|
142
|
+
high?: number;
|
|
143
|
+
/** The place of the lowest reading, the first where several tie. */
|
|
144
|
+
low?: number;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Where the highest and the lowest readings stand. A blank slot is passed
|
|
148
|
+
* over, and a list with nothing in it has neither.
|
|
149
|
+
*
|
|
150
|
+
* Sparkline's overview and Heatmap's busiest place each looked for these.
|
|
151
|
+
*/
|
|
152
|
+
export declare const extremesOf: (slots: readonly (number | undefined)[]) => Extremes;
|
|
@@ -139,3 +139,18 @@ export const readRows = (table) => {
|
|
|
139
139
|
return [...(element.tBodies[0]?.rows ?? [])].map((one) => row(one.cells[0], [...one.cells].slice(1), names));
|
|
140
140
|
};
|
|
141
141
|
export const seriesKey = (name) => name.trim().toLowerCase();
|
|
142
|
+
export const extremesOf = (slots) => {
|
|
143
|
+
const found = {};
|
|
144
|
+
slots.forEach((value, at) => {
|
|
145
|
+
if (value === undefined) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (found.high === undefined || value > slots[found.high]) {
|
|
149
|
+
found.high = at;
|
|
150
|
+
}
|
|
151
|
+
if (found.low === undefined || value < slots[found.low]) {
|
|
152
|
+
found.low = at;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return found;
|
|
156
|
+
};
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long each unit lasts, in milliseconds, largest last. `elapsedTime`
|
|
3
|
+
* reads its minute, hour and day from here.
|
|
4
|
+
*/
|
|
5
|
+
export declare const UNITS: Array<{
|
|
6
|
+
ms: number;
|
|
7
|
+
unit: Intl.RelativeTimeFormatUnit;
|
|
8
|
+
}>;
|
|
1
9
|
/** What one timestamp reads as, and when that stops being true. */
|
|
2
10
|
export interface Relative {
|
|
3
11
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const held = new Map();
|
|
2
|
-
const UNITS = [
|
|
2
|
+
export const UNITS = [
|
|
3
3
|
{ ms: 1000, unit: 'second' },
|
|
4
4
|
{ ms: 60_000, unit: 'minute' },
|
|
5
5
|
{ ms: 3_600_000, unit: 'hour' },
|
|
@@ -25,7 +25,10 @@ export const relativeTime = (locale, options = {}) => {
|
|
|
25
25
|
}
|
|
26
26
|
const size = Math.abs(elapsed);
|
|
27
27
|
if (size < 60_000) {
|
|
28
|
-
return {
|
|
28
|
+
return {
|
|
29
|
+
next: Math.max(1000, 60_000 - size),
|
|
30
|
+
text: found.format(elapsed > 0 ? 0 : -0, 'second'),
|
|
31
|
+
};
|
|
29
32
|
}
|
|
30
33
|
let chosen = UNITS[1];
|
|
31
34
|
for (const unit of UNITS) {
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
+
import { callListener } from '../call-listener/call-listener.js';
|
|
1
2
|
export const RENDER_SLOT = Symbol.for('fusion.dev/helix/render');
|
|
2
3
|
export const measuring = () => typeof globalThis[RENDER_SLOT] === 'function';
|
|
3
|
-
let told = false;
|
|
4
4
|
export const reportRender = (element, start, duration) => {
|
|
5
|
-
|
|
6
|
-
if (typeof listener !== 'function') {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
try {
|
|
10
|
-
listener(element, { duration, start });
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
if (!told) {
|
|
14
|
-
told = true;
|
|
15
|
-
console.error('helix: a render listener threw, and the element rendered as usual. Later failures are not printed.', error);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
5
|
+
callListener(RENDER_SLOT, 'render', 'the element rendered as usual', element, { duration, start });
|
|
18
6
|
};
|
|
@@ -18,8 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
/** What went wrong, stable across versions, so it can be counted. */
|
|
20
20
|
export type RuntimeKind =
|
|
21
|
+
/** A Blockquote's `cite` holds words, where it takes the source's URL. */
|
|
22
|
+
'cite-not-url'
|
|
23
|
+
/** A deprecated property, attribute or slot was used; the message names what replaces it. */
|
|
24
|
+
| 'deprecated'
|
|
21
25
|
/** Another class asked for a tag that is already registered: two copies of Helix. */
|
|
22
|
-
'duplicate-registration'
|
|
26
|
+
| 'duplicate-registration'
|
|
23
27
|
/** The browser refused fullscreen, usually for a frame without the permission. */
|
|
24
28
|
| 'fullscreen-refused'
|
|
25
29
|
/** A rating's `min` is no score between 0 and its `max`. */
|
|
@@ -41,6 +45,8 @@ export type RuntimeKind =
|
|
|
41
45
|
| 'missing-row-key'
|
|
42
46
|
/** A stencil names parts the shape does not have. */
|
|
43
47
|
| 'missing-stencil-parts'
|
|
48
|
+
/** A chart, a legend or a sparkline names no table with `for`. Development builds only. */
|
|
49
|
+
| 'missing-table'
|
|
44
50
|
/** A Skip Link's `href` names no element in its tree, so a press moves focus nowhere. */
|
|
45
51
|
| 'missing-target'
|
|
46
52
|
/** A tooltip's `for` names no element in its own tree. */
|
|
@@ -75,8 +81,12 @@ export type RuntimeKind =
|
|
|
75
81
|
| 'unlinked-trigger'
|
|
76
82
|
/** A Text is rendered as a label, a deprecated value whose shadow root reaches no control. */
|
|
77
83
|
| 'unreachable-label'
|
|
84
|
+
/** A QR code holds a logo at a correction level below `H`, so it may not scan. */
|
|
85
|
+
| 'unscannable-logo'
|
|
78
86
|
/** A virtualized list's box has no height of its own, so its window never settles. */
|
|
79
87
|
| 'unsettled-window'
|
|
88
|
+
/** A native button in a Button Group with `selection-mode`, which cannot be told it is pressed. */
|
|
89
|
+
| 'untold-toggle'
|
|
80
90
|
/** A slotted trigger is a plain element, which cannot be told it opens something. */
|
|
81
91
|
| 'untold-trigger'
|
|
82
92
|
/** A tooltip wraps its trigger, so its text reaches no screen reader. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { callListener } from '../call-listener/call-listener.js';
|
|
1
2
|
export const RUNTIME_SLOT = Symbol.for('fusion.dev/helix/runtime');
|
|
2
3
|
export const RUNTIME_LOG = Symbol.for('fusion.dev/helix/runtime-log');
|
|
3
4
|
const KEPT = 200;
|
|
4
|
-
let told = false;
|
|
5
5
|
export const reportRuntime = (event) => {
|
|
6
6
|
const store = globalThis;
|
|
7
7
|
const log = Array.isArray(store[RUNTIME_LOG]) ? store[RUNTIME_LOG] : [];
|
|
@@ -9,19 +9,7 @@ export const reportRuntime = (event) => {
|
|
|
9
9
|
log.push(event);
|
|
10
10
|
}
|
|
11
11
|
store[RUNTIME_LOG] = log;
|
|
12
|
-
|
|
13
|
-
const listener = store[RUNTIME_SLOT];
|
|
14
|
-
if (typeof listener === 'function') {
|
|
15
|
-
try {
|
|
16
|
-
quiet = listener(event) === true;
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
if (!told) {
|
|
20
|
-
told = true;
|
|
21
|
-
console.error('helix: a runtime listener threw, and the warning is printed here. Later failures are not printed.', error);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
12
|
+
const quiet = callListener(RUNTIME_SLOT, 'runtime', 'the warning is printed here', event) === true;
|
|
25
13
|
if (quiet) {
|
|
26
14
|
return;
|
|
27
15
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VERSION } from '../../version.js';
|
|
2
|
+
import { callListener } from '../call-listener/call-listener.js';
|
|
2
3
|
export const USAGE_SLOT = Symbol.for('fusion.dev/helix/usage');
|
|
3
4
|
export const VERSIONS_SLOT = Symbol.for('fusion.dev/helix/versions');
|
|
4
5
|
export const noteVersion = () => {
|
|
@@ -9,19 +10,6 @@ export const noteVersion = () => {
|
|
|
9
10
|
}
|
|
10
11
|
store[VERSIONS_SLOT] = listed;
|
|
11
12
|
};
|
|
12
|
-
let told = false;
|
|
13
13
|
export const reportUsage = (element) => {
|
|
14
|
-
|
|
15
|
-
if (typeof listener !== 'function') {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
listener(element);
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
if (!told) {
|
|
23
|
-
told = true;
|
|
24
|
-
console.error('helix: a usage listener threw, and the element rendered as usual. Later failures are not printed.', error);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
14
|
+
callListener(USAGE_SLOT, 'usage', 'the element rendered as usual', element);
|
|
27
15
|
};
|
|
@@ -9,13 +9,25 @@
|
|
|
9
9
|
* sent the first and Date Picker the second, so what a server received for the
|
|
10
10
|
* same answer depended on which control asked for it.
|
|
11
11
|
*
|
|
12
|
-
* **The joined string is the state**, which is what the browser
|
|
13
|
-
* `formStateRestoreCallback` and what `value` holds, so a
|
|
14
|
-
* reads the shape it wrote.
|
|
12
|
+
* **The joined string is the state by default**, which is what the browser
|
|
13
|
+
* hands back to `formStateRestoreCallback` and what `value` holds, so a
|
|
14
|
+
* restored control reads the shape it wrote. A control whose answers can hold
|
|
15
|
+
* a comma asks for `state: 'entries'`, and the browser hands back the form
|
|
16
|
+
* data itself, one entry per answer, so `b,1` comes back as one answer and not
|
|
17
|
+
* two.
|
|
15
18
|
*
|
|
16
19
|
* **Nothing without a `name`.** A plain string is skipped by the form when its
|
|
17
20
|
* control has no name, but `FormData` carries its own names and its entries
|
|
18
21
|
* are appended whatever the element is called, so an unnamed control would
|
|
19
22
|
* submit empty-named values.
|
|
20
23
|
*/
|
|
21
|
-
|
|
24
|
+
/** How `reportValues` saves the state the browser restores. */
|
|
25
|
+
export interface ReportValuesOptions {
|
|
26
|
+
/**
|
|
27
|
+
* What the browser hands back to `formStateRestoreCallback`: `joined`, the
|
|
28
|
+
* answers joined with commas, or `entries`, the form data with one entry per
|
|
29
|
+
* answer. Defaults to `joined`.
|
|
30
|
+
*/
|
|
31
|
+
state?: 'entries' | 'joined';
|
|
32
|
+
}
|
|
33
|
+
export declare const reportValues: (internals: ElementInternals, name: string, values: readonly string[], { state }?: ReportValuesOptions) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const reportValues = (internals, name, values) => {
|
|
1
|
+
export const reportValues = (internals, name, values, { state = 'joined' } = {}) => {
|
|
2
2
|
if (name === '' || values.length === 0) {
|
|
3
3
|
internals.setFormValue(null);
|
|
4
4
|
return;
|
|
@@ -7,5 +7,5 @@ export const reportValues = (internals, name, values) => {
|
|
|
7
7
|
for (const value of values) {
|
|
8
8
|
data.append(name, value);
|
|
9
9
|
}
|
|
10
|
-
internals.setFormValue(data, values.join(','));
|
|
10
|
+
internals.setFormValue(data, state === 'entries' ? data : values.join(','));
|
|
11
11
|
};
|
|
@@ -35,3 +35,14 @@ export interface ChangeRequestOptions<T> {
|
|
|
35
35
|
next: T;
|
|
36
36
|
}
|
|
37
37
|
export declare const requestChange: <T>({ apply, event, host, next, }: ChangeRequestOptions<T>) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Asks before a reader's press moves a value, then applies it and says so:
|
|
40
|
+
* `helix-before-change` with the value it would leave, which a listener may
|
|
41
|
+
* cancel, and `change` once it has moved.
|
|
42
|
+
*
|
|
43
|
+
* **The order every selectable set in the library keeps**, so a page reads the
|
|
44
|
+
* same pair from a list, a tag group, a heatmap or a legend: the cancelable
|
|
45
|
+
* question first, the platform's `change` after, bubbling and composed as a
|
|
46
|
+
* native control's does. Returns whether the value moved.
|
|
47
|
+
*/
|
|
48
|
+
export declare const requestValue: (host: EventTarget, next: string, apply: (value: string) => void) => boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HelixBeforeChangeEvent } from '../../events/before-change.js';
|
|
1
2
|
export const requestChange = ({ apply, event, host, next, }) => {
|
|
2
3
|
if (!host.dispatchEvent(event(next))) {
|
|
3
4
|
return false;
|
|
@@ -5,3 +6,12 @@ export const requestChange = ({ apply, event, host, next, }) => {
|
|
|
5
6
|
apply(next);
|
|
6
7
|
return true;
|
|
7
8
|
};
|
|
9
|
+
export const requestValue = (host, next, apply) => requestChange({
|
|
10
|
+
apply: (value) => {
|
|
11
|
+
apply(value);
|
|
12
|
+
host.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
13
|
+
},
|
|
14
|
+
event: (value) => new HelixBeforeChangeEvent({ value }),
|
|
15
|
+
host,
|
|
16
|
+
next,
|
|
17
|
+
});
|