@fusion.dev/helix-core 0.143.0 → 0.144.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +9098 -3887
- package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +10 -0
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +5 -0
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +89 -0
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +102 -1
- package/dist/controllers/chart-pointer-controller/reading-step.d.ts +57 -0
- package/dist/controllers/chart-pointer-controller/reading-step.js +45 -0
- package/dist/controllers/copy-controller/copy-controller.d.ts +11 -6
- package/dist/controllers/copy-controller/copy-controller.js +11 -9
- package/dist/controllers/delay-controller/delay-controller.d.ts +50 -0
- package/dist/controllers/delay-controller/delay-controller.js +54 -0
- package/dist/controllers/focus-scope-controller/focus-scope-controller.d.ts +56 -0
- package/dist/controllers/focus-scope-controller/focus-scope-controller.js +71 -0
- package/dist/controllers/open-phase-controller/open-phase-controller.d.ts +49 -0
- package/dist/controllers/open-phase-controller/open-phase-controller.js +41 -0
- package/dist/controllers/source-controller/source-controller.d.ts +16 -0
- package/dist/controllers/source-controller/source-controller.js +44 -1
- package/dist/controllers/status-controller/status-controller.d.ts +47 -0
- package/dist/controllers/status-controller/status-controller.js +34 -0
- package/dist/controllers/swipe-controller/swipe-controller.d.ts +61 -0
- package/dist/controllers/swipe-controller/swipe-controller.js +106 -0
- package/dist/events/events.d.ts +35 -0
- package/dist/events/events.js +12 -0
- package/dist/index.d.ts +28 -8
- package/dist/index.js +27 -7
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +7 -2
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +5 -1
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +10 -0
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +9 -0
- package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +2 -0
- package/dist/mixins/mixin-dismissible/mixin-dismissible.js +7 -3
- package/dist/mixins/mixin-field/mixin-field.d.ts +2 -0
- package/dist/mixins/mixin-field/mixin-field.js +8 -0
- package/dist/mixins/mixin-picker-editing/mixin-picker-editing.d.ts +16 -6
- package/dist/mixins/mixin-picker-editing/mixin-picker-editing.js +19 -1
- package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +5 -4
- package/dist/mixins/mixin-picker-field/mixin-picker-field.js +12 -1
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +67 -0
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +243 -0
- package/dist/utils/arrow/arrow.d.ts +15 -0
- package/dist/utils/arrow/arrow.js +2 -0
- package/dist/utils/audit-name/audit-name.d.ts +28 -0
- package/dist/utils/audit-name/audit-name.js +95 -0
- package/dist/utils/calendar/calendar.d.ts +117 -0
- package/dist/utils/calendar/calendar.js +96 -0
- package/dist/utils/chord/chord.d.ts +43 -0
- package/dist/utils/chord/chord.js +53 -0
- package/dist/utils/clock/clock.d.ts +87 -0
- package/dist/utils/clock/clock.js +68 -0
- package/dist/utils/color-name/color-name.d.ts +28 -0
- package/dist/utils/color-name/color-name.js +150 -0
- package/dist/utils/count-label/count-label.d.ts +13 -0
- package/dist/utils/count-label/count-label.js +5 -0
- package/dist/utils/css-color/css-color.d.ts +12 -0
- package/dist/utils/css-color/css-color.js +2 -2
- package/dist/utils/date-segments/date-segments.d.ts +122 -0
- package/dist/utils/date-segments/date-segments.js +206 -0
- package/dist/utils/draw-dismiss/draw-dismiss.d.ts +30 -0
- package/dist/utils/draw-dismiss/draw-dismiss.js +16 -0
- package/dist/utils/draw-tooltip/draw-tooltip.d.ts +15 -21
- package/dist/utils/draw-tooltip/draw-tooltip.js +11 -5
- package/dist/utils/draw-tooltip/spoken-reading.d.ts +10 -0
- package/dist/utils/draw-tooltip/spoken-reading.js +10 -0
- package/dist/utils/follow-link/follow-link.d.ts +46 -0
- package/dist/utils/follow-link/follow-link.js +37 -0
- package/dist/utils/heading-in/heading-in.d.ts +19 -0
- package/dist/utils/heading-in/heading-in.js +14 -0
- package/dist/utils/number-format/number-format.d.ts +23 -0
- package/dist/utils/number-format/number-format.js +55 -0
- package/dist/utils/platform/platform.d.ts +23 -0
- package/dist/utils/platform/platform.js +11 -0
- package/dist/utils/read-color/read-color.d.ts +14 -0
- package/dist/utils/read-color/read-color.js +13 -0
- package/dist/utils/read-series/read-series.d.ts +6 -0
- package/dist/utils/read-series/read-series.js +1 -0
- package/dist/utils/reflect-elements/reflect-elements.d.ts +37 -0
- package/dist/utils/reflect-elements/reflect-elements.js +40 -0
- package/dist/utils/relative-time/relative-time.d.ts +13 -1
- package/dist/utils/relative-time/relative-time.js +7 -4
- package/dist/utils/report-runtime/report-runtime.d.ts +2 -0
- package/dist/utils/roving-stop/roving-stop.d.ts +20 -0
- package/dist/utils/roving-stop/roving-stop.js +3 -0
- package/dist/utils/swipe/swipe.d.ts +63 -0
- package/dist/utils/swipe/swipe.js +48 -0
- package/dist/utils/tab-stops/tab-stops.d.ts +16 -0
- package/dist/utils/tab-stops/tab-stops.js +22 -0
- package/dist/utils/value-as/value-as.d.ts +20 -0
- package/dist/utils/value-as/value-as.js +16 -0
- package/dist/utils/write-time/write-time.d.ts +11 -1
- package/dist/utils/write-time/write-time.js +7 -7
- package/dist/utils/written-list/written-list.d.ts +18 -0
- package/dist/utils/written-list/written-list.js +8 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -138,3 +138,4 @@ export const readRows = (table) => {
|
|
|
138
138
|
.map((one) => one.textContent?.trim() ?? '');
|
|
139
139
|
return [...(element.tBodies[0]?.rows ?? [])].map((one) => row(one.cells[0], [...one.cells].slice(1), names));
|
|
140
140
|
};
|
|
141
|
+
export const seriesKey = (name) => name.trim().toLowerCase();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** The ARIA properties that take elements rather than ids. */
|
|
2
|
+
export type ElementsProperty = 'ariaDescribedByElements' | 'ariaLabelledByElements';
|
|
3
|
+
/**
|
|
4
|
+
* Writes elements onto an ARIA element-reflection property of `target`, or
|
|
5
|
+
* clears it, and says whether the property is now this caller's.
|
|
6
|
+
*
|
|
7
|
+
* Element references, because the words are in the light DOM and the panel in
|
|
8
|
+
* a shadow root, which an IDREF cannot cross. Nothing is written where the
|
|
9
|
+
* engine has no reflection, which keeps the fallback name, and the answer is
|
|
10
|
+
* then `owned` as it was. **An empty list clears the property only where the
|
|
11
|
+
* caller wrote it**, which `owned` says, so a name another hand wrote stays.
|
|
12
|
+
*
|
|
13
|
+
* Dialog and Drawer name their panel by its header this way, Popover by its
|
|
14
|
+
* first heading, Dialog describes itself by its body, and a Tour stop names
|
|
15
|
+
* and describes its panel.
|
|
16
|
+
*/
|
|
17
|
+
export declare const reflectElements: (target: Element | null | undefined, property: ElementsProperty, elements: readonly Element[], owned: boolean) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Adds `by` to the elements that describe `target`, where that is safe, and
|
|
20
|
+
* says whether `target` is now described by it.
|
|
21
|
+
*
|
|
22
|
+
* Through element reflection, and only where no framework is watching:
|
|
23
|
+
*
|
|
24
|
+
* - **Inside a shadow root**, such as the `<button>` a Helix button draws, it
|
|
25
|
+
* is always safe. That markup is the component's own, and no server sent it.
|
|
26
|
+
* - **In the light DOM**, the element is the consumer's, and reflection writes
|
|
27
|
+
* an empty `aria-describedby` onto it. So it is written only when `light` is
|
|
28
|
+
* set, which a caller does once a reader has acted on the page, after any
|
|
29
|
+
* hydration, and never where the consumer wrote `aria-describedby` or
|
|
30
|
+
* `aria-labelledby` themselves, which are theirs to keep.
|
|
31
|
+
*
|
|
32
|
+
* Existing references are kept, so a field's hint still describes it. Tooltip
|
|
33
|
+
* describes its trigger this way.
|
|
34
|
+
*/
|
|
35
|
+
export declare const linkDescription: (target: Element, by: Element, light: boolean) => boolean;
|
|
36
|
+
/** Takes `by` back off the elements that describe `target`, leaving the rest. */
|
|
37
|
+
export declare const unlinkDescription: (target: Element, by: Element) => void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const reflectElements = (target, property, elements, owned) => {
|
|
2
|
+
const element = target;
|
|
3
|
+
if (!element || !(property in element)) {
|
|
4
|
+
return owned;
|
|
5
|
+
}
|
|
6
|
+
if (elements.length > 0) {
|
|
7
|
+
element[property] = elements;
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (owned) {
|
|
11
|
+
element[property] = null;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
};
|
|
15
|
+
export const linkDescription = (target, by, light) => {
|
|
16
|
+
const element = target;
|
|
17
|
+
if (!('ariaDescribedByElements' in element)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const current = element.ariaDescribedByElements ?? [];
|
|
21
|
+
if (current.includes(by)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const shadowed = typeof ShadowRoot !== 'undefined' && target.getRootNode() instanceof ShadowRoot;
|
|
25
|
+
if (!shadowed &&
|
|
26
|
+
(!light || target.hasAttribute('aria-describedby') || target.hasAttribute('aria-labelledby'))) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
element.ariaDescribedByElements = [...current, by];
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
export const unlinkDescription = (target, by) => {
|
|
33
|
+
const element = target;
|
|
34
|
+
const current = element.ariaDescribedByElements;
|
|
35
|
+
if (!current?.includes(by)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const rest = current.filter((one) => one !== by);
|
|
39
|
+
element.ariaDescribedByElements = rest.length > 0 ? rest : null;
|
|
40
|
+
};
|
|
@@ -13,6 +13,15 @@ export interface Relative {
|
|
|
13
13
|
/** The reading, in the caller's locale. */
|
|
14
14
|
text: string;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* How a reading is written: `style` is `Intl.RelativeTimeFormat`'s, from
|
|
18
|
+
* "3 hours ago" at `long` to "3h ago" at `narrow` in English, and `numeric`
|
|
19
|
+
* says whether a day back is "yesterday" (`auto`) or "1 day ago" (`always`).
|
|
20
|
+
*/
|
|
21
|
+
export interface RelativeOptions {
|
|
22
|
+
numeric?: 'always' | 'auto';
|
|
23
|
+
style?: 'long' | 'narrow' | 'short';
|
|
24
|
+
}
|
|
16
25
|
/**
|
|
17
26
|
* A formatter for one locale, kept.
|
|
18
27
|
*
|
|
@@ -20,5 +29,8 @@ export interface Relative {
|
|
|
20
29
|
* testable at all: a function that reads `Date.now()` internally can only be
|
|
21
30
|
* tested by waiting or by replacing a global, and both produce specs that fail
|
|
22
31
|
* on a slow machine.
|
|
32
|
+
*
|
|
33
|
+
* Long and `auto` unless `options` says otherwise, which is what a person
|
|
34
|
+
* says aloud and what every caller wanted before Format Date offered the rest.
|
|
23
35
|
*/
|
|
24
|
-
export declare const relativeTime: (locale: string) => ((from: Date, now: Date) => Relative);
|
|
36
|
+
export declare const relativeTime: (locale: string, options?: RelativeOptions) => ((from: Date, now: Date) => Relative);
|
|
@@ -8,11 +8,14 @@ const UNITS = [
|
|
|
8
8
|
{ ms: 2_629_800_000, unit: 'month' },
|
|
9
9
|
{ ms: 31_557_600_000, unit: 'year' },
|
|
10
10
|
];
|
|
11
|
-
export const relativeTime = (locale) => {
|
|
12
|
-
|
|
11
|
+
export const relativeTime = (locale, options = {}) => {
|
|
12
|
+
const numeric = options.numeric ?? 'auto';
|
|
13
|
+
const style = options.style ?? 'long';
|
|
14
|
+
const key = `${locale}|${numeric}|${style}`;
|
|
15
|
+
let format = held.get(key);
|
|
13
16
|
if (!format) {
|
|
14
|
-
format = new Intl.RelativeTimeFormat(locale || undefined, { numeric
|
|
15
|
-
held.set(
|
|
17
|
+
format = new Intl.RelativeTimeFormat(locale || undefined, { numeric, style });
|
|
18
|
+
held.set(key, format);
|
|
16
19
|
}
|
|
17
20
|
const found = format;
|
|
18
21
|
return (from, now) => {
|
|
@@ -32,6 +32,8 @@ export type RuntimeKind =
|
|
|
32
32
|
| 'misplaced-handler'
|
|
33
33
|
/** The three.js driver could not load its layout, so the notes stay where they started. */
|
|
34
34
|
| 'missing-layout'
|
|
35
|
+
/** A control has no accessible name, so a screen reader announces it by its role alone. */
|
|
36
|
+
| 'missing-name'
|
|
35
37
|
/** A selectable Data Table has no `row-key`, so it knows each row by its place. */
|
|
36
38
|
| 'missing-row-key'
|
|
37
39
|
/** A stencil names parts the shape does not have. */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** How `rovingStop` reads an item. */
|
|
2
|
+
export interface RovingOptions<T> {
|
|
3
|
+
/** Whether an item cannot take the stop. Defaults to none. */
|
|
4
|
+
disabled?: (item: T) => boolean;
|
|
5
|
+
/** Whether an item is chosen, which takes the stop before the first. Defaults to none. */
|
|
6
|
+
selected?: (item: T) => boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The item that holds a group's one tab stop: the one the reader was last on,
|
|
10
|
+
* then the first chosen one, then the first that can be reached.
|
|
11
|
+
*
|
|
12
|
+
* A composite widget is one Tab stop whose items the arrow keys move between,
|
|
13
|
+
* and Tab coming back lands where the reader left. A disabled item never holds
|
|
14
|
+
* it, so a group whose first item is disabled can still be entered from the
|
|
15
|
+
* keyboard. `last` is ignored once it has left `items`.
|
|
16
|
+
*
|
|
17
|
+
* Tag Group, a Toggle Group that presses several, and a Notifications feed
|
|
18
|
+
* choose their stop this way.
|
|
19
|
+
*/
|
|
20
|
+
export declare const rovingStop: <T>(items: readonly T[], last: T | undefined, { disabled, selected }?: RovingOptions<T>) => T | undefined;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const rovingStop = (items, last, { disabled = () => false, selected = () => false } = {}) => (last !== undefined && items.includes(last) && !disabled(last) ? last : undefined) ??
|
|
2
|
+
items.find((item) => selected(item) && !disabled(item)) ??
|
|
3
|
+
items.find((item) => !disabled(item));
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** The edge of the window a surface leaves through when it is swiped away. */
|
|
2
|
+
export type SwipeEdge = 'bottom' | 'left' | 'right' | 'top';
|
|
3
|
+
/** Which way along the screen a swipe is read. */
|
|
4
|
+
export type SwipeAxis = 'x' | 'y';
|
|
5
|
+
/**
|
|
6
|
+
* How fast a release has to be moving, in pixels per millisecond, to count as
|
|
7
|
+
* a fling: the surface then goes on in the direction it was thrown, whatever
|
|
8
|
+
* distance it had covered.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FLING = 0.5;
|
|
11
|
+
/**
|
|
12
|
+
* The edge a surface placed at `placement` leaves through, resolved against the
|
|
13
|
+
* writing direction. `start` and `end` are logical, so on a right-to-left page
|
|
14
|
+
* the end is the left edge.
|
|
15
|
+
*/
|
|
16
|
+
export declare const swipeEdge: (placement: 'bottom' | 'end' | 'start' | 'top', rtl: boolean) => SwipeEdge;
|
|
17
|
+
/** The axis a swipe toward `edge` is read along. */
|
|
18
|
+
export declare const swipeAxis: (edge: SwipeEdge) => SwipeAxis;
|
|
19
|
+
/** How far a movement of `dx` and `dy` went toward `edge`, negative when it went away. */
|
|
20
|
+
export declare const towardEdge: (edge: SwipeEdge, dx: number, dy: number) => number;
|
|
21
|
+
/** A distance toward `edge` as a physical offset, for a `translate`. */
|
|
22
|
+
export declare const edgeOffset: (edge: SwipeEdge, travel: number) => {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The fractions of a surface a reader can leave on screen, read from a list
|
|
28
|
+
* such as `"0.5 1"`. Each is above zero and at most one, and the list is sorted
|
|
29
|
+
* with no repeats. Where nothing usable is written, the one point is `1`: the
|
|
30
|
+
* whole surface.
|
|
31
|
+
*/
|
|
32
|
+
export declare const parseSnapPoints: (text: string | null | undefined) => number[];
|
|
33
|
+
/** What a release asks of `settleSwipe`. */
|
|
34
|
+
export interface SwipeRelease {
|
|
35
|
+
/** The snap points, from `parseSnapPoints`. */
|
|
36
|
+
points: readonly number[];
|
|
37
|
+
/** The surface's size along the axis, in pixels. */
|
|
38
|
+
size: number;
|
|
39
|
+
/** How far the surface sits toward its edge, from fully shown, in pixels. */
|
|
40
|
+
travel: number;
|
|
41
|
+
/** How fast it was moving toward its edge at the release, in pixels per millisecond. */
|
|
42
|
+
velocity: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The fraction of the surface to rest at after a release, and `0` to dismiss it.
|
|
46
|
+
*
|
|
47
|
+
* A fling goes on to the next point in the direction it was thrown, and
|
|
48
|
+
* flinging toward the edge from the smallest point dismisses. Otherwise the
|
|
49
|
+
* surface rests at the nearest point, dismissal counted as the point at zero, so
|
|
50
|
+
* a surface with the one point `1` goes once it is dragged more than halfway.
|
|
51
|
+
*/
|
|
52
|
+
export declare const settleSwipe: ({ points, size, travel, velocity }: SwipeRelease) => number;
|
|
53
|
+
/** How far toward its edge a surface resting at `fraction` sits, in pixels. */
|
|
54
|
+
export declare const snapTravel: (size: number, fraction: number) => number;
|
|
55
|
+
/**
|
|
56
|
+
* Which way a swipe across a row of items moves: `1` to the next, `-1` to the
|
|
57
|
+
* previous, `0` to stay.
|
|
58
|
+
*
|
|
59
|
+
* Dragging the picture toward the start of the line brings the next one in,
|
|
60
|
+
* so the next item is a drag leftward on a left-to-right page and rightward on
|
|
61
|
+
* a right-to-left one. A quarter of the width, or a fling, is enough.
|
|
62
|
+
*/
|
|
63
|
+
export declare const swipeStep: (dx: number, vx: number, width: number, rtl: boolean) => -1 | 0 | 1;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const FLING = 0.5;
|
|
2
|
+
export const swipeEdge = (placement, rtl) => {
|
|
3
|
+
if (placement === 'top' || placement === 'bottom') {
|
|
4
|
+
return placement;
|
|
5
|
+
}
|
|
6
|
+
return (placement === 'end') !== rtl ? 'right' : 'left';
|
|
7
|
+
};
|
|
8
|
+
export const swipeAxis = (edge) => edge === 'left' || edge === 'right' ? 'x' : 'y';
|
|
9
|
+
export const towardEdge = (edge, dx, dy) => ({ bottom: dy, left: -dx, right: dx, top: -dy })[edge];
|
|
10
|
+
export const edgeOffset = (edge, travel) => ({
|
|
11
|
+
x: edge === 'right' ? travel : edge === 'left' ? -travel : 0,
|
|
12
|
+
y: edge === 'bottom' ? travel : edge === 'top' ? -travel : 0,
|
|
13
|
+
});
|
|
14
|
+
export const parseSnapPoints = (text) => {
|
|
15
|
+
const points = [
|
|
16
|
+
...new Set((text ?? '')
|
|
17
|
+
.split(/[\s,]+/)
|
|
18
|
+
.map(Number)
|
|
19
|
+
.filter((one) => Number.isFinite(one) && one > 0 && one <= 1)),
|
|
20
|
+
].sort((a, b) => a - b);
|
|
21
|
+
return points.length > 0 ? points : [1];
|
|
22
|
+
};
|
|
23
|
+
export const settleSwipe = ({ points, size, travel, velocity }) => {
|
|
24
|
+
if (size <= 0) {
|
|
25
|
+
return points[points.length - 1] ?? 1;
|
|
26
|
+
}
|
|
27
|
+
const shown = Math.max(0, Math.min(1, 1 - travel / size));
|
|
28
|
+
const stops = [0, ...points];
|
|
29
|
+
if (velocity >= FLING) {
|
|
30
|
+
return [...stops].reverse().find((one) => one < shown) ?? 0;
|
|
31
|
+
}
|
|
32
|
+
if (velocity <= -FLING) {
|
|
33
|
+
return stops.find((one) => one > shown) ?? stops[stops.length - 1] ?? 1;
|
|
34
|
+
}
|
|
35
|
+
return stops.reduce((best, one) => (Math.abs(one - shown) < Math.abs(best - shown) ? one : best));
|
|
36
|
+
};
|
|
37
|
+
export const snapTravel = (size, fraction) => Math.round((1 - fraction) * size);
|
|
38
|
+
export const swipeStep = (dx, vx, width, rtl) => {
|
|
39
|
+
const along = rtl ? dx : -dx;
|
|
40
|
+
const speed = rtl ? vx : -vx;
|
|
41
|
+
if (speed >= FLING || (speed > -FLING && along > width / 4)) {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
if (speed <= -FLING || along < -width / 4) {
|
|
45
|
+
return -1;
|
|
46
|
+
}
|
|
47
|
+
return 0;
|
|
48
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every element in `root` that Tab stops at, in the order Tab reaches them,
|
|
3
|
+
* through shadow roots and slots.
|
|
4
|
+
*
|
|
5
|
+
* Through them because a panel's controls are inside the component it holds,
|
|
6
|
+
* and often a button's inside that, so a light-DOM query such as
|
|
7
|
+
* `focusableElements` finds none of them. Walked in the composed tree's order:
|
|
8
|
+
* a host's shadow root in place of its children, and a slot's assigned
|
|
9
|
+
* elements in place of its fallback. A host that delegates its focus is
|
|
10
|
+
* passed through to what it delegates to, and anything `hidden`, `inert`,
|
|
11
|
+
* disabled or without a box is left out.
|
|
12
|
+
*
|
|
13
|
+
* `MixinPickerField` keeps Tab inside a picker's panel with it, reading the
|
|
14
|
+
* first and last of these as the ends of the dialog.
|
|
15
|
+
*/
|
|
16
|
+
export declare const tabStops: (root: Element) => HTMLElement[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const tabStops = (root) => {
|
|
2
|
+
const found = [];
|
|
3
|
+
const visit = (node) => {
|
|
4
|
+
const element = node;
|
|
5
|
+
if (element.hidden || element.inert) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (element.tabIndex >= 0 &&
|
|
9
|
+
!element.shadowRoot?.delegatesFocus &&
|
|
10
|
+
!element.disabled &&
|
|
11
|
+
element.getClientRects().length > 0) {
|
|
12
|
+
found.push(element);
|
|
13
|
+
}
|
|
14
|
+
const assigned = node instanceof HTMLSlotElement ? node.assignedElements() : [];
|
|
15
|
+
const children = assigned.length > 0 ? assigned : [...(node.shadowRoot ?? node).children];
|
|
16
|
+
for (const child of children) {
|
|
17
|
+
visit(child);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
visit(root);
|
|
21
|
+
return found;
|
|
22
|
+
};
|
|
@@ -55,6 +55,26 @@ export declare const valueAsMinutes: (value: string) => number | null;
|
|
|
55
55
|
export declare const minutesAsValue: (minutes: number) => string;
|
|
56
56
|
/** Whether a value is a time these controls can hold, which is minute precision. */
|
|
57
57
|
export declare const isTimeValue: (value: string) => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The seconds since midnight a value names, or `null` where it is not a time.
|
|
60
|
+
*
|
|
61
|
+
* **To the second, which is what Time Picker and Time Input hold under
|
|
62
|
+
* `seconds`.** `valueAsMinutes` above stays at minute precision for the
|
|
63
|
+
* controls that never carry one. Both shapes are read whatever a control
|
|
64
|
+
* writes, so a value with seconds handed to a field without them is a time it
|
|
65
|
+
* can judge, off its step, rather than text it cannot read.
|
|
66
|
+
*/
|
|
67
|
+
export declare const valueAsSeconds: (value: string) => number | null;
|
|
68
|
+
/**
|
|
69
|
+
* Whether a value is a time a control holds: `HH:mm`, or either shape where it
|
|
70
|
+
* holds `seconds`.
|
|
71
|
+
*/
|
|
72
|
+
export declare const isClockValue: (value: string, seconds?: boolean) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Seconds since midnight as `HH:mm`, or `HH:mm:ss` with `seconds`, wrapping
|
|
75
|
+
* rather than running off the day.
|
|
76
|
+
*/
|
|
77
|
+
export declare const secondsAsValue: (at: number, seconds?: boolean) => string;
|
|
58
78
|
/**
|
|
59
79
|
* The number a value holds, or `NaN`.
|
|
60
80
|
*
|
|
@@ -53,5 +53,21 @@ export const minutesAsValue = (minutes) => {
|
|
|
53
53
|
return `${String(Math.floor(whole / 60)).padStart(2, '0')}:${String(whole % 60).padStart(2, '0')}`;
|
|
54
54
|
};
|
|
55
55
|
export const isTimeValue = (value) => valueAsMinutes(value) !== null;
|
|
56
|
+
const CLOCK = /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/;
|
|
57
|
+
const DAY_SECONDS = 86_400;
|
|
58
|
+
export const valueAsSeconds = (value) => {
|
|
59
|
+
const parts = CLOCK.exec(value.trim());
|
|
60
|
+
if (!parts) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return Number(parts[1]) * 3600 + Number(parts[2]) * 60 + Number(parts[3] ?? 0);
|
|
64
|
+
};
|
|
65
|
+
export const isClockValue = (value, seconds = false) => valueAsSeconds(value) !== null && (seconds || value.trim().length === 5);
|
|
66
|
+
export const secondsAsValue = (at, seconds = false) => {
|
|
67
|
+
const whole = ((Math.round(at) % DAY_SECONDS) + DAY_SECONDS) % DAY_SECONDS;
|
|
68
|
+
const pad = (part) => String(part).padStart(2, '0');
|
|
69
|
+
const clock = `${pad(Math.floor(whole / 3600))}:${pad(Math.floor(whole / 60) % 60)}`;
|
|
70
|
+
return seconds ? `${clock}:${pad(whole % 60)}` : clock;
|
|
71
|
+
};
|
|
56
72
|
export const valueAsNumber = (value) => value.trim() === '' ? Number.NaN : Number(value);
|
|
57
73
|
export const numberAsValue = (count) => (Number.isNaN(count) ? '' : String(count));
|
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
* 24 hour regardless.
|
|
10
10
|
*
|
|
11
11
|
* A value this cannot read is handed back unchanged, so a field showing what is
|
|
12
|
-
* being typed shows the letters rather than blanking under the reader.
|
|
12
|
+
* being typed shows the letters rather than blanking under the reader. Minute
|
|
13
|
+
* precision, as `valueAsMinutes` reads: `writeClock` is the same writing to the
|
|
14
|
+
* second.
|
|
13
15
|
*/
|
|
14
16
|
export declare const writeTime: (value: string, locale?: string) => string;
|
|
17
|
+
/**
|
|
18
|
+
* A time written the way the locale writes one, with its seconds where asked.
|
|
19
|
+
*
|
|
20
|
+
* Reads both `HH:mm` and `HH:mm:ss`, as `valueAsSeconds` does, which is what
|
|
21
|
+
* Time Picker and Time Input hold; a locale with its clock set by `clockLocale`
|
|
22
|
+
* writes on that clock. A value that is not a time is handed back unchanged.
|
|
23
|
+
*/
|
|
24
|
+
export declare const writeClock: (value: string, locale: string, seconds?: boolean) => string;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { valueAsMinutes } from '../value-as/value-as.js';
|
|
2
|
-
export const writeTime = (value, locale) =>
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { valueAsMinutes, valueAsSeconds } from '../value-as/value-as.js';
|
|
2
|
+
export const writeTime = (value, locale) => valueAsMinutes(value) === null ? value : writeClock(value, locale ?? '');
|
|
3
|
+
export const writeClock = (value, locale, seconds = false) => {
|
|
4
|
+
const at = valueAsSeconds(value);
|
|
5
|
+
if (at === null) {
|
|
5
6
|
return value;
|
|
6
7
|
}
|
|
7
|
-
const at = new Date(Date.UTC(2000, 0, 1, Math.floor(minutes / 60), minutes % 60));
|
|
8
8
|
return new Intl.DateTimeFormat(locale || undefined, {
|
|
9
|
-
timeStyle: 'short',
|
|
9
|
+
timeStyle: seconds ? 'medium' : 'short',
|
|
10
10
|
timeZone: 'UTC',
|
|
11
|
-
}).format(at);
|
|
11
|
+
}).format(new Date(at * 1000));
|
|
12
12
|
};
|
|
@@ -43,3 +43,21 @@ export declare const writtenList: (source: WrittenList) => string[];
|
|
|
43
43
|
* playback rate, and only the caller knows that a rate of zero is not a rate.
|
|
44
44
|
*/
|
|
45
45
|
export declare const writtenNumbers: (source: WrittenList) => number[];
|
|
46
|
+
/**
|
|
47
|
+
* A value list: the entries between commas, trimmed, with the empty ones
|
|
48
|
+
* dropped.
|
|
49
|
+
*
|
|
50
|
+
* **Commas only**, which is the value rule `writtenList` describes beside it: a
|
|
51
|
+
* value may hold a space, and `value="New York, Paris"` is two cities. Select,
|
|
52
|
+
* Listbox, Checkbox Group, Tag Group, Toggle Group, Date Picker, Time Picker
|
|
53
|
+
* and Legend's hidden series all read their lists this way.
|
|
54
|
+
*/
|
|
55
|
+
export declare const commaList: (value: string) => string[];
|
|
56
|
+
/**
|
|
57
|
+
* A list with `value` taken out where it is in it, or added at the end where
|
|
58
|
+
* it is not.
|
|
59
|
+
*
|
|
60
|
+
* With `order`, the result is put in that order and holds only what `order`
|
|
61
|
+
* holds, so a set of pressed items reads the same whichever was pressed first.
|
|
62
|
+
*/
|
|
63
|
+
export declare const toggleValue: (list: readonly string[], value: string, order?: readonly string[]) => string[];
|
|
@@ -4,3 +4,11 @@ export const writtenList = (source) => (Array.isArray(source) ? source.join(' ')
|
|
|
4
4
|
export const writtenNumbers = (source) => writtenList(source)
|
|
5
5
|
.map(Number)
|
|
6
6
|
.filter((one) => Number.isFinite(one));
|
|
7
|
+
export const commaList = (value) => value
|
|
8
|
+
.split(',')
|
|
9
|
+
.map((one) => one.trim())
|
|
10
|
+
.filter((one) => one !== '');
|
|
11
|
+
export const toggleValue = (list, value, order) => {
|
|
12
|
+
const next = list.includes(value) ? list.filter((one) => one !== value) : [...list, value];
|
|
13
|
+
return order ? order.filter((one) => next.includes(one)) : next;
|
|
14
|
+
};
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.144.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.144.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.144.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@fusion.dev/helix-cem": "0.
|
|
41
|
-
"@fusion.dev/helix-testing": "0.
|
|
40
|
+
"@fusion.dev/helix-cem": "0.144.0",
|
|
41
|
+
"@fusion.dev/helix-testing": "0.144.0",
|
|
42
42
|
"@lit-labs/ssr": "^4.1.0",
|
|
43
43
|
"lit": "^3.2.1"
|
|
44
44
|
},
|