@primitiv-ui/core 0.1.60 → 0.1.62
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/dist/checkbox-card/policy.d.ts +13 -0
- package/dist/checkbox-card/policy.js +10 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/radio-group/policy.d.ts +52 -0
- package/dist/radio-group/policy.js +35 -0
- package/dist/segmented-control/policy.d.ts +1 -1
- package/dist/segmented-control/policy.js +5 -9
- package/dist/slider/policy.d.ts +77 -0
- package/dist/slider/policy.js +102 -0
- package/dist/split-button/policy.d.ts +12 -0
- package/dist/split-button/policy.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A checkbox's state: checked, unchecked, or `"indeterminate"` (mixed). */
|
|
2
|
+
export type CheckboxCardCheckedState = boolean | "indeterminate";
|
|
3
|
+
/** Everything a checkbox card's toggle depends on, as plain data. */
|
|
4
|
+
export type CheckboxCardState = {
|
|
5
|
+
checked: CheckboxCardCheckedState;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The state a press on a checkbox card asks for: the opposite of checked, with
|
|
10
|
+
* a mixed card becoming checked — a press always resolves the mix. `null` while
|
|
11
|
+
* the card is disabled, so no change is reported.
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkboxCardToggle(state: CheckboxCardState): boolean | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The state a press on a checkbox card asks for: the opposite of checked, with
|
|
3
|
+
* a mixed card becoming checked — a press always resolves the mix. `null` while
|
|
4
|
+
* the card is disabled, so no change is reported.
|
|
5
|
+
*/
|
|
6
|
+
export function checkboxCardToggle(state) {
|
|
7
|
+
if (state.disabled)
|
|
8
|
+
return null;
|
|
9
|
+
return state.checked === "indeterminate" ? true : !state.checked;
|
|
10
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,11 @@ export { resolveRovingTarget, type ResolveRovingTargetOptions, } from "./keyboar
|
|
|
4
4
|
export { tabsClick, tabsKeydown, tabsTabStop, type TabsActivationMode, type TabsKeyOutcome, type TabsOrientation, type TabsReadingDirection, type TabsState, } from "./tabs/policy.js";
|
|
5
5
|
export { accordionKeydown, accordionToggle, type AccordionKeyOutcome, type AccordionOrientation, type AccordionReadingDirection, type AccordionState, } from "./accordion/policy.js";
|
|
6
6
|
export { toggleGroupKeydown, toggleGroupTabStop, toggleGroupToggle, type ToggleGroupKeyOutcome, type ToggleGroupOrientation, type ToggleGroupReadingDirection, type ToggleGroupState, } from "./toggle-group/policy.js";
|
|
7
|
+
export { radioGroupKeydown, radioGroupSelect, radioGroupTabStop, type RadioGroupKeyOutcome, type RadioGroupOrientation, type RadioGroupReadingDirection, type RadioGroupState, } from "./radio-group/policy.js";
|
|
7
8
|
export { segmentedControlKeydown, segmentedControlSelect, segmentedControlTabStop, type SegmentedControlKeyOutcome, type SegmentedControlOrientation, type SegmentedControlReadingDirection, type SegmentedControlState, } from "./segmented-control/policy.js";
|
|
9
|
+
export { checkboxCardToggle, type CheckboxCardCheckedState, type CheckboxCardState, } from "./checkbox-card/policy.js";
|
|
10
|
+
export { splitButtonOpensMenu, type SplitButtonActionKey } from "./split-button/policy.js";
|
|
11
|
+
export { sliderClosestThumb, sliderEdge, sliderKeyValue, sliderPercent, sliderPointerValue, sliderRangeInsets, sliderSetThumb, sliderSnap, sliderThumbInset, type SliderAxis, type SliderEdge, type SliderInset, type SliderOrientation, type SliderReadingDirection, type SliderScale, } from "./slider/policy.js";
|
|
8
12
|
export { collapsibleKeydown, collapsibleToggle, type CollapsibleKeyOutcome, type CollapsibleState, } from "./collapsible/policy.js";
|
|
9
13
|
export { FOCUSABLE_SELECTOR } from "./focus/focusable.js";
|
|
10
14
|
export { MENU_ITEM_SELECTOR, MENU_TYPEAHEAD_RESET_MS, menuKeydown, menuTypeahead, type MenuKeyOutcome, type MenuKeyState, type MenuTypeaheadOutcome, type MenuTypeaheadState, } from "./menu/policy.js";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,11 @@ export { resolveRovingTarget, } from "./keyboard/resolveRovingTarget.js";
|
|
|
4
4
|
export { tabsClick, tabsKeydown, tabsTabStop, } from "./tabs/policy.js";
|
|
5
5
|
export { accordionKeydown, accordionToggle, } from "./accordion/policy.js";
|
|
6
6
|
export { toggleGroupKeydown, toggleGroupTabStop, toggleGroupToggle, } from "./toggle-group/policy.js";
|
|
7
|
+
export { radioGroupKeydown, radioGroupSelect, radioGroupTabStop, } from "./radio-group/policy.js";
|
|
7
8
|
export { segmentedControlKeydown, segmentedControlSelect, segmentedControlTabStop, } from "./segmented-control/policy.js";
|
|
9
|
+
export { checkboxCardToggle, } from "./checkbox-card/policy.js";
|
|
10
|
+
export { splitButtonOpensMenu } from "./split-button/policy.js";
|
|
11
|
+
export { sliderClosestThumb, sliderEdge, sliderKeyValue, sliderPercent, sliderPointerValue, sliderRangeInsets, sliderSetThumb, sliderSnap, sliderThumbInset, } from "./slider/policy.js";
|
|
8
12
|
export { collapsibleKeydown, collapsibleToggle, } from "./collapsible/policy.js";
|
|
9
13
|
export { FOCUSABLE_SELECTOR } from "./focus/focusable.js";
|
|
10
14
|
export { MENU_ITEM_SELECTOR, MENU_TYPEAHEAD_RESET_MS, menuKeydown, menuTypeahead, } from "./menu/policy.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Which arrow pairs move between items: one axis, or `"both"` for all four
|
|
2
|
+
* arrows (a grid of cards that wraps). */
|
|
3
|
+
export type RadioGroupOrientation = "horizontal" | "vertical" | "both";
|
|
4
|
+
/** Reading direction. Under `"rtl"` the horizontal arrows are mirrored. */
|
|
5
|
+
export type RadioGroupReadingDirection = "ltr" | "rtl";
|
|
6
|
+
/**
|
|
7
|
+
* Everything a radio group's selection and keyboard rules depend on, as plain
|
|
8
|
+
* data — shared by every single-select radio-group component (SegmentedControl,
|
|
9
|
+
* RadioCard). Each surface builds it from wherever it keeps state and applies
|
|
10
|
+
* the outcome back the same way. A whole-group disable is every value in
|
|
11
|
+
* `disabled`.
|
|
12
|
+
*/
|
|
13
|
+
export type RadioGroupState = {
|
|
14
|
+
/** Every item's value, in DOM order, disabled ones included. */
|
|
15
|
+
values: readonly string[];
|
|
16
|
+
/** The values of the disabled items. */
|
|
17
|
+
disabled: ReadonlySet<string>;
|
|
18
|
+
/** The selected item's value, or `undefined` while none is selected. */
|
|
19
|
+
selected: string | undefined;
|
|
20
|
+
orientation: RadioGroupOrientation;
|
|
21
|
+
dir: RadioGroupReadingDirection;
|
|
22
|
+
};
|
|
23
|
+
/** What a key press on an item asks for: an item to focus, and the item to
|
|
24
|
+
* select — `null` when it is already selected, so no change is reported. */
|
|
25
|
+
export type RadioGroupKeyOutcome = {
|
|
26
|
+
focus: string;
|
|
27
|
+
select: string | null;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The item a click on `value` selects: `null` when it is disabled or already
|
|
31
|
+
* selected. A selected item never lets go, so the selection cannot be cleared
|
|
32
|
+
* to nothing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function radioGroupSelect(state: Pick<RadioGroupState, "disabled" | "selected">, value: string): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* The WAI-ARIA radio group's keyboard contract for a key press on the item
|
|
37
|
+
* `focused`: the arrows follow `orientation` (all four under `"both"`), the
|
|
38
|
+
* horizontal pair mirrored under RTL, wrap at the ends and skip disabled items,
|
|
39
|
+
* and each one moves focus **and** selection. While focus sits on a disabled
|
|
40
|
+
* item they do nothing. There is no Home / End, and Enter / Space are the
|
|
41
|
+
* native button's click.
|
|
42
|
+
*
|
|
43
|
+
* Returns `null` for a key the group does not handle, which tells the caller to
|
|
44
|
+
* leave the event alone (no `preventDefault`).
|
|
45
|
+
*/
|
|
46
|
+
export declare function radioGroupKeydown(state: RadioGroupState, focused: string, key: string): RadioGroupKeyOutcome | null;
|
|
47
|
+
/**
|
|
48
|
+
* The item that takes part in the page's Tab order (the roving tab stop): the
|
|
49
|
+
* selected item, else the first enabled one so the group is still reachable by
|
|
50
|
+
* keyboard — or none, while nothing is selected and every item is disabled.
|
|
51
|
+
*/
|
|
52
|
+
export declare function radioGroupTabStop(state: Pick<RadioGroupState, "values" | "disabled" | "selected">): string | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getKeyToActionMap } from "../keyboard/getKeyToActionMap.js";
|
|
2
|
+
import { resolveRovingTarget } from "../keyboard/resolveRovingTarget.js";
|
|
3
|
+
/**
|
|
4
|
+
* The item a click on `value` selects: `null` when it is disabled or already
|
|
5
|
+
* selected. A selected item never lets go, so the selection cannot be cleared
|
|
6
|
+
* to nothing.
|
|
7
|
+
*/
|
|
8
|
+
export function radioGroupSelect(state, value) {
|
|
9
|
+
return state.disabled.has(value) || state.selected === value ? null : value;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The WAI-ARIA radio group's keyboard contract for a key press on the item
|
|
13
|
+
* `focused`: the arrows follow `orientation` (all four under `"both"`), the
|
|
14
|
+
* horizontal pair mirrored under RTL, wrap at the ends and skip disabled items,
|
|
15
|
+
* and each one moves focus **and** selection. While focus sits on a disabled
|
|
16
|
+
* item they do nothing. There is no Home / End, and Enter / Space are the
|
|
17
|
+
* native button's click.
|
|
18
|
+
*
|
|
19
|
+
* Returns `null` for a key the group does not handle, which tells the caller to
|
|
20
|
+
* leave the event alone (no `preventDefault`).
|
|
21
|
+
*/
|
|
22
|
+
export function radioGroupKeydown(state, focused, key) {
|
|
23
|
+
const action = getKeyToActionMap({ orientation: state.orientation, dir: state.dir })[key];
|
|
24
|
+
const navigable = state.values.filter((value) => !state.disabled.has(value));
|
|
25
|
+
const result = resolveRovingTarget({ action, navigable, currentKey: focused });
|
|
26
|
+
return result && { focus: result.target, select: radioGroupSelect(state, result.target) };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The item that takes part in the page's Tab order (the roving tab stop): the
|
|
30
|
+
* selected item, else the first enabled one so the group is still reachable by
|
|
31
|
+
* keyboard — or none, while nothing is selected and every item is disabled.
|
|
32
|
+
*/
|
|
33
|
+
export function radioGroupTabStop(state) {
|
|
34
|
+
return state.selected ?? state.values.find((value) => !state.disabled.has(value));
|
|
35
|
+
}
|
|
@@ -27,7 +27,7 @@ export type SegmentedControlKeyOutcome = {
|
|
|
27
27
|
/**
|
|
28
28
|
* The segment a click on `value` selects: `null` when it is disabled or
|
|
29
29
|
* already selected. A selected segment never lets go, so — unlike a toggle
|
|
30
|
-
* group — the selection cannot be cleared to nothing.
|
|
30
|
+
* group — the selection cannot be cleared to nothing. The radio group's rule.
|
|
31
31
|
*/
|
|
32
32
|
export declare function segmentedControlSelect(state: Pick<SegmentedControlState, "disabled" | "selected">, value: string): string | null;
|
|
33
33
|
/**
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolveRovingTarget } from "../keyboard/resolveRovingTarget.js";
|
|
1
|
+
import { radioGroupKeydown, radioGroupSelect, radioGroupTabStop } from "../radio-group/policy.js";
|
|
3
2
|
/**
|
|
4
3
|
* The segment a click on `value` selects: `null` when it is disabled or
|
|
5
4
|
* already selected. A selected segment never lets go, so — unlike a toggle
|
|
6
|
-
* group — the selection cannot be cleared to nothing.
|
|
5
|
+
* group — the selection cannot be cleared to nothing. The radio group's rule.
|
|
7
6
|
*/
|
|
8
7
|
export function segmentedControlSelect(state, value) {
|
|
9
|
-
return
|
|
8
|
+
return radioGroupSelect(state, value);
|
|
10
9
|
}
|
|
11
10
|
/**
|
|
12
11
|
* SegmentedControl's keyboard contract — the WAI-ARIA radio group's — for a key
|
|
@@ -20,10 +19,7 @@ export function segmentedControlSelect(state, value) {
|
|
|
20
19
|
* to leave the event alone (no `preventDefault`).
|
|
21
20
|
*/
|
|
22
21
|
export function segmentedControlKeydown(state, focused, key) {
|
|
23
|
-
|
|
24
|
-
const navigable = state.values.filter((value) => !state.disabled.has(value));
|
|
25
|
-
const result = resolveRovingTarget({ action, navigable, currentKey: focused });
|
|
26
|
-
return result && { focus: result.target, select: segmentedControlSelect(state, result.target) };
|
|
22
|
+
return radioGroupKeydown(state, focused, key);
|
|
27
23
|
}
|
|
28
24
|
/**
|
|
29
25
|
* The segment that takes part in the page's Tab order (the roving tab stop):
|
|
@@ -32,5 +28,5 @@ export function segmentedControlKeydown(state, focused, key) {
|
|
|
32
28
|
* is disabled.
|
|
33
29
|
*/
|
|
34
30
|
export function segmentedControlTabStop(state) {
|
|
35
|
-
return
|
|
31
|
+
return radioGroupTabStop(state);
|
|
36
32
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Layout axis of the slider. */
|
|
2
|
+
export type SliderOrientation = "horizontal" | "vertical";
|
|
3
|
+
/** Reading direction. Under `"rtl"` a horizontal slider runs right to left. */
|
|
4
|
+
export type SliderReadingDirection = "ltr" | "rtl";
|
|
5
|
+
/** Which way a slider runs: its axis, reading direction, and whether it is inverted. */
|
|
6
|
+
export type SliderAxis = {
|
|
7
|
+
orientation: SliderOrientation;
|
|
8
|
+
dir: SliderReadingDirection;
|
|
9
|
+
/** Run from the opposite end: max at the start edge. */
|
|
10
|
+
inverted: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** The values a slider takes: `min` to `max` in `step`s. */
|
|
13
|
+
export type SliderScale = {
|
|
14
|
+
min: number;
|
|
15
|
+
max: number;
|
|
16
|
+
step: number;
|
|
17
|
+
};
|
|
18
|
+
/** A physical edge of the track. */
|
|
19
|
+
export type SliderEdge = "left" | "right" | "top" | "bottom";
|
|
20
|
+
/** A distance from one edge of the track, as a percentage of its length. */
|
|
21
|
+
export type SliderInset = {
|
|
22
|
+
edge: SliderEdge;
|
|
23
|
+
percent: number;
|
|
24
|
+
};
|
|
25
|
+
/** `value` rounded to the nearest step counted from `min`, at the step's own precision. */
|
|
26
|
+
export declare function sliderSnap(value: number, min: number, step: number): number;
|
|
27
|
+
/** Where `value` sits between `min` and `max`, as a percentage clamped to 0–100. */
|
|
28
|
+
export declare function sliderPercent(value: number, min: number, max: number): number;
|
|
29
|
+
/**
|
|
30
|
+
* The edge a slider measures from — where its `min` sits: the left (right under
|
|
31
|
+
* RTL) of a horizontal track, the bottom of a vertical one, and the opposite
|
|
32
|
+
* edge when inverted.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sliderEdge({ orientation, dir, inverted }: SliderAxis): SliderEdge;
|
|
35
|
+
/** Where a thumb at `value` sits: its distance from the start edge. */
|
|
36
|
+
export declare function sliderThumbInset(value: number, { min, max }: Pick<SliderScale, "min" | "max">, axis: SliderAxis): SliderInset;
|
|
37
|
+
/**
|
|
38
|
+
* Where the filled range sits: from the start edge to a single thumb, or between
|
|
39
|
+
* the outermost of several — as its two insets, from the start edge and from the
|
|
40
|
+
* opposite one.
|
|
41
|
+
*/
|
|
42
|
+
export declare function sliderRangeInsets(values: readonly number[], { min, max }: Pick<SliderScale, "min" | "max">, axis: SliderAxis): {
|
|
43
|
+
start: SliderInset;
|
|
44
|
+
end: SliderInset;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The value under a pointer at `point` on a track whose box is `rect`: read
|
|
48
|
+
* along the axis from the start edge, clamped to the track, snapped to the step
|
|
49
|
+
* and kept within the scale.
|
|
50
|
+
*/
|
|
51
|
+
export declare function sliderPointerValue(point: {
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
}, rect: {
|
|
55
|
+
left: number;
|
|
56
|
+
width: number;
|
|
57
|
+
bottom: number;
|
|
58
|
+
height: number;
|
|
59
|
+
}, { min, max, step, orientation, dir, inverted }: SliderScale & SliderAxis): number;
|
|
60
|
+
/**
|
|
61
|
+
* The WAI-ARIA slider keys for a thumb at `value`: the arrows step one `step`
|
|
62
|
+
* (up and toward the end increase, mirrored under RTL and when inverted),
|
|
63
|
+
* Page Up / Page Down ten, Home / End jump to `min` / `max`. The result is
|
|
64
|
+
* snapped to the step but not clamped — the thumb's own bounds do that. `null`
|
|
65
|
+
* for any other key.
|
|
66
|
+
*/
|
|
67
|
+
export declare function sliderKeyValue(key: string, value: number, { min, max, step, orientation, dir, inverted }: SliderScale & SliderAxis): number | null;
|
|
68
|
+
/**
|
|
69
|
+
* Every thumb's value after moving thumb `index` to `value`: kept within the
|
|
70
|
+
* scale and between its neighbours, `minStepsBetweenThumbs` steps apart. `null`
|
|
71
|
+
* when the thumb would not move, so no change is reported.
|
|
72
|
+
*/
|
|
73
|
+
export declare function sliderSetThumb(values: readonly number[], index: number, value: number, { min, max, step, minStepsBetweenThumbs }: SliderScale & {
|
|
74
|
+
minStepsBetweenThumbs: number;
|
|
75
|
+
}): number[] | null;
|
|
76
|
+
/** The index of the thumb nearest `value`, the first on a tie. */
|
|
77
|
+
export declare function sliderClosestThumb(value: number, values: readonly number[]): number;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const OPPOSITE = { left: "right", right: "left", top: "bottom", bottom: "top" };
|
|
2
|
+
function clamp(value, min, max) {
|
|
3
|
+
return Math.min(Math.max(value, min), max);
|
|
4
|
+
}
|
|
5
|
+
/** `value` rounded to the nearest step counted from `min`, at the step's own precision. */
|
|
6
|
+
export function sliderSnap(value, min, step) {
|
|
7
|
+
const snapped = min + Math.round((value - min) / step) * step;
|
|
8
|
+
const decimals = String(step).split(".")[1]?.length ?? 0;
|
|
9
|
+
return Number(snapped.toFixed(decimals));
|
|
10
|
+
}
|
|
11
|
+
/** Where `value` sits between `min` and `max`, as a percentage clamped to 0–100. */
|
|
12
|
+
export function sliderPercent(value, min, max) {
|
|
13
|
+
return clamp(((value - min) / (max - min)) * 100, 0, 100);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The edge a slider measures from — where its `min` sits: the left (right under
|
|
17
|
+
* RTL) of a horizontal track, the bottom of a vertical one, and the opposite
|
|
18
|
+
* edge when inverted.
|
|
19
|
+
*/
|
|
20
|
+
export function sliderEdge({ orientation, dir, inverted }) {
|
|
21
|
+
const start = orientation === "vertical" ? "bottom" : dir === "rtl" ? "right" : "left";
|
|
22
|
+
return inverted ? OPPOSITE[start] : start;
|
|
23
|
+
}
|
|
24
|
+
/** Where a thumb at `value` sits: its distance from the start edge. */
|
|
25
|
+
export function sliderThumbInset(value, { min, max }, axis) {
|
|
26
|
+
return { edge: sliderEdge(axis), percent: sliderPercent(value, min, max) };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Where the filled range sits: from the start edge to a single thumb, or between
|
|
30
|
+
* the outermost of several — as its two insets, from the start edge and from the
|
|
31
|
+
* opposite one.
|
|
32
|
+
*/
|
|
33
|
+
export function sliderRangeInsets(values, { min, max }, axis) {
|
|
34
|
+
const edge = sliderEdge(axis);
|
|
35
|
+
const percents = values.map((value) => sliderPercent(value, min, max));
|
|
36
|
+
const start = values.length > 1 ? Math.min(...percents) : 0;
|
|
37
|
+
return { start: { edge, percent: start }, end: { edge: OPPOSITE[edge], percent: 100 - Math.max(...percents) } };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The value under a pointer at `point` on a track whose box is `rect`: read
|
|
41
|
+
* along the axis from the start edge, clamped to the track, snapped to the step
|
|
42
|
+
* and kept within the scale.
|
|
43
|
+
*/
|
|
44
|
+
export function sliderPointerValue(point, rect, { min, max, step, orientation, dir, inverted }) {
|
|
45
|
+
const vertical = orientation === "vertical";
|
|
46
|
+
const fromStart = vertical ? (rect.bottom - point.y) / rect.height : (point.x - rect.left) / rect.width;
|
|
47
|
+
const reversed = vertical ? inverted : (dir === "rtl") !== inverted;
|
|
48
|
+
const percent = clamp(reversed ? 1 - fromStart : fromStart, 0, 1);
|
|
49
|
+
return clamp(sliderSnap(min + percent * (max - min), min, step), min, max);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The WAI-ARIA slider keys for a thumb at `value`: the arrows step one `step`
|
|
53
|
+
* (up and toward the end increase, mirrored under RTL and when inverted),
|
|
54
|
+
* Page Up / Page Down ten, Home / End jump to `min` / `max`. The result is
|
|
55
|
+
* snapped to the step but not clamped — the thumb's own bounds do that. `null`
|
|
56
|
+
* for any other key.
|
|
57
|
+
*/
|
|
58
|
+
export function sliderKeyValue(key, value, { min, max, step, orientation, dir, inverted }) {
|
|
59
|
+
if (key === "Home")
|
|
60
|
+
return min;
|
|
61
|
+
if (key === "End")
|
|
62
|
+
return max;
|
|
63
|
+
let increases;
|
|
64
|
+
if (key === "PageUp" || key === "PageDown") {
|
|
65
|
+
increases = key === "PageUp";
|
|
66
|
+
}
|
|
67
|
+
else if (key === "ArrowUp" || key === "ArrowDown") {
|
|
68
|
+
increases = (key === "ArrowUp") !== (orientation === "vertical" && inverted);
|
|
69
|
+
}
|
|
70
|
+
else if (key === "ArrowRight" || key === "ArrowLeft") {
|
|
71
|
+
const rightIncreases = orientation === "vertical" || (dir === "ltr") !== inverted;
|
|
72
|
+
increases = (key === "ArrowRight") === rightIncreases;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const magnitude = key.startsWith("Page") ? step * 10 : step;
|
|
78
|
+
return sliderSnap(value + (increases ? magnitude : -magnitude), min, step);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Every thumb's value after moving thumb `index` to `value`: kept within the
|
|
82
|
+
* scale and between its neighbours, `minStepsBetweenThumbs` steps apart. `null`
|
|
83
|
+
* when the thumb would not move, so no change is reported.
|
|
84
|
+
*/
|
|
85
|
+
export function sliderSetThumb(values, index, value, { min, max, step, minStepsBetweenThumbs }) {
|
|
86
|
+
const gap = minStepsBetweenThumbs * step;
|
|
87
|
+
const lower = index > 0 ? values[index - 1] + gap : min;
|
|
88
|
+
const upper = index < values.length - 1 ? values[index + 1] - gap : max;
|
|
89
|
+
const next = clamp(value, lower, upper);
|
|
90
|
+
if (values[index] === next)
|
|
91
|
+
return null;
|
|
92
|
+
return values.map((entry, i) => (i === index ? next : entry));
|
|
93
|
+
}
|
|
94
|
+
/** The index of the thumb nearest `value`, the first on a tie. */
|
|
95
|
+
export function sliderClosestThumb(value, values) {
|
|
96
|
+
let closest = 0;
|
|
97
|
+
values.forEach((thumb, index) => {
|
|
98
|
+
if (Math.abs(thumb - value) < Math.abs(values[closest] - value))
|
|
99
|
+
closest = index;
|
|
100
|
+
});
|
|
101
|
+
return closest;
|
|
102
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Everything a key on a split button's action depends on, as plain data. */
|
|
2
|
+
export type SplitButtonActionKey = {
|
|
3
|
+
key: string;
|
|
4
|
+
disabled: boolean;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Whether a key on the action half opens the menu: ArrowDown does, the
|
|
8
|
+
* standard split-button affordance for reaching the alternatives without
|
|
9
|
+
* tabbing past the action. Every other key is the action's own, and a
|
|
10
|
+
* disabled action opens nothing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function splitButtonOpensMenu({ key, disabled }: SplitButtonActionKey): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a key on the action half opens the menu: ArrowDown does, the
|
|
3
|
+
* standard split-button affordance for reaching the alternatives without
|
|
4
|
+
* tabbing past the action. Every other key is the action's own, and a
|
|
5
|
+
* disabled action opens nothing.
|
|
6
|
+
*/
|
|
7
|
+
export function splitButtonOpensMenu({ key, disabled }) {
|
|
8
|
+
return !disabled && key === "ArrowDown";
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitiv-ui/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.62",
|
|
4
4
|
"description": "The framework-free behaviour core shared by every Primitiv surface — keyboard maps, roving-focus targets, id derivation and per-component policy, as pure functions with no DOM and no dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|