@fusion.dev/helix-core 0.147.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 +1741 -133
- 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/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/helix-element/helix-element.d.ts +14 -5
- package/dist/helix-element/helix-element.js +4 -2
- package/dist/index.d.ts +24 -10
- package/dist/index.js +22 -8
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +5 -0
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +3 -0
- 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/autofill-select/autofill-select.d.ts +40 -0
- package/dist/utils/autofill-select/autofill-select.js +14 -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/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/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/number-format/number-format.d.ts +7 -0
- package/dist/utils/number-format/number-format.js +1 -0
- package/dist/utils/report-runtime/report-runtime.d.ts +7 -1
- 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/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/package.json +4 -4
|
@@ -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;
|
|
@@ -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);
|
|
@@ -18,8 +18,10 @@
|
|
|
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'
|
|
21
23
|
/** A deprecated property, attribute or slot was used; the message names what replaces it. */
|
|
22
|
-
'deprecated'
|
|
24
|
+
| 'deprecated'
|
|
23
25
|
/** Another class asked for a tag that is already registered: two copies of Helix. */
|
|
24
26
|
| 'duplicate-registration'
|
|
25
27
|
/** The browser refused fullscreen, usually for a frame without the permission. */
|
|
@@ -79,8 +81,12 @@ export type RuntimeKind =
|
|
|
79
81
|
| 'unlinked-trigger'
|
|
80
82
|
/** A Text is rendered as a label, a deprecated value whose shadow root reaches no control. */
|
|
81
83
|
| 'unreachable-label'
|
|
84
|
+
/** A QR code holds a logo at a correction level below `H`, so it may not scan. */
|
|
85
|
+
| 'unscannable-logo'
|
|
82
86
|
/** A virtualized list's box has no height of its own, so its window never settles. */
|
|
83
87
|
| 'unsettled-window'
|
|
88
|
+
/** A native button in a Button Group with `selection-mode`, which cannot be told it is pressed. */
|
|
89
|
+
| 'untold-toggle'
|
|
84
90
|
/** A slotted trigger is a plain element, which cannot be told it opens something. */
|
|
85
91
|
| 'untold-trigger'
|
|
86
92
|
/** A tooltip wraps its trigger, so its text reaches no screen reader. */
|
|
@@ -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
|
+
});
|
|
@@ -27,6 +27,23 @@ export interface ScrollItemOptions {
|
|
|
27
27
|
* bar with it.
|
|
28
28
|
*/
|
|
29
29
|
export declare const scrollingAncestor: (item: HTMLElement, options?: ScrollerOptions) => HTMLElement | null;
|
|
30
|
+
/** What `scrollRoom` takes off a box that scrolls. */
|
|
31
|
+
export interface ScrollRoomOptions {
|
|
32
|
+
/**
|
|
33
|
+
* How much of the box its own content covers while it scrolls, a sticky row
|
|
34
|
+
* of headings, in pixels. Defaults to `0`.
|
|
35
|
+
*/
|
|
36
|
+
covered?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* How much of a list a reader sees at once, for Page Up and Page Down: the
|
|
40
|
+
* box's own height where it scrolls, less what covers it, and the window's
|
|
41
|
+
* where it does not and the page scrolls instead.
|
|
42
|
+
*
|
|
43
|
+
* **A pixel of slack**, since a box whose content rounds to one pixel more
|
|
44
|
+
* than its height does not scroll, and was paging by the window's height.
|
|
45
|
+
*/
|
|
46
|
+
export declare const scrollRoom: (box: Element | null | undefined, { covered }?: ScrollRoomOptions) => number;
|
|
30
47
|
/** Where `scrollingAncestor` looks, and what it takes for a scroller. */
|
|
31
48
|
export interface ScrollerOptions {
|
|
32
49
|
/** The axis whose overflow counts, or either where absent. */
|
|
@@ -15,6 +15,7 @@ export const scrollingAncestor = (item, options = {}) => {
|
|
|
15
15
|
}
|
|
16
16
|
return null;
|
|
17
17
|
};
|
|
18
|
+
export const scrollRoom = (box, { covered = 0 } = {}) => box && box.scrollHeight > box.clientHeight + 1 ? box.clientHeight - covered : window.innerHeight;
|
|
18
19
|
const overflows = (node, axis) => {
|
|
19
20
|
const tall = node.scrollHeight > node.clientHeight;
|
|
20
21
|
const wide = node.scrollWidth > node.clientWidth;
|
|
@@ -22,3 +22,11 @@
|
|
|
22
22
|
* anything", and a slot showing its fallback does render.
|
|
23
23
|
*/
|
|
24
24
|
export declare const slotFilled: (slot: HTMLSlotElement | null | undefined) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The slot of a name in a shadow root, or its default slot for `''`.
|
|
27
|
+
*
|
|
28
|
+
* `slot[name=""]` matches nothing, since the default slot has no `name`
|
|
29
|
+
* attribute at all, which is the one thing every hand-written lookup had to
|
|
30
|
+
* remember.
|
|
31
|
+
*/
|
|
32
|
+
export declare const slotNamed: (root: ParentNode | null | undefined, name: string) => HTMLSlotElement | null;
|
|
@@ -1 +1,2 @@
|
|
|
1
1
|
export const slotFilled = (slot) => (slot?.assignedNodes({ flatten: true }) ?? []).some((node) => node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim() !== '');
|
|
2
|
+
export const slotNamed = (root, name) => root?.querySelector?.(name === '' ? 'slot:not([name])' : `slot[name="${name}"]`) ?? null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The toolbar an element sits in, or `null`.
|
|
3
|
+
*
|
|
4
|
+
* Read from the parent up, so a toolbar is never its own. One answer for every
|
|
5
|
+
* family that changes inside a toolbar: Toolbar becomes a group of the outer
|
|
6
|
+
* one, a multiple Toggle Group becomes a group, and Divider turns across the
|
|
7
|
+
* toolbar's axis. By tag and by role, since a family other than Toolbar cannot
|
|
8
|
+
* import Toolbar's class; Toolbar adds its own class for a third tag.
|
|
9
|
+
*
|
|
10
|
+
* `closest` stops at a shadow root, which is what a slotted child wants: its
|
|
11
|
+
* toolbar is its light-DOM parent or above.
|
|
12
|
+
*/
|
|
13
|
+
export declare const toolbarAround: (element: Element) => Element | null;
|
|
14
|
+
/**
|
|
15
|
+
* The axis a toolbar runs along: its own `orientation`, else the page's
|
|
16
|
+
* `aria-orientation`, else horizontal, which is the role's default.
|
|
17
|
+
*/
|
|
18
|
+
export declare const toolbarAxis: (toolbar: Element) => 'horizontal' | 'vertical';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const TOOLBAR = '[role="toolbar"], helix-toolbar, helix-headless-toolbar';
|
|
2
|
+
export const toolbarAround = (element) => element.parentElement?.closest(TOOLBAR) ?? null;
|
|
3
|
+
export const toolbarAxis = (toolbar) => (toolbar.getAttribute('orientation') ?? toolbar.getAttribute('aria-orientation')) === 'vertical'
|
|
4
|
+
? 'vertical'
|
|
5
|
+
: 'horizontal';
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.148.0';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-core",
|
|
3
3
|
"description": "Helix element primitives: base classes, mixins, decorators, utilities.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.148.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@floating-ui/dom": "^1.8.0",
|
|
37
|
-
"@fusion.dev/helix-icons": "0.
|
|
37
|
+
"@fusion.dev/helix-icons": "0.148.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@fusion.dev/helix-cem": "0.
|
|
41
|
-
"@fusion.dev/helix-testing": "0.
|
|
40
|
+
"@fusion.dev/helix-cem": "0.148.0",
|
|
41
|
+
"@fusion.dev/helix-testing": "0.148.0",
|
|
42
42
|
"@lit-labs/ssr": "^4.1.0",
|
|
43
43
|
"lit": "^3.2.1"
|
|
44
44
|
},
|