@dorsk/tsumikit 0.67.1 → 0.68.1
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/README.md +96 -3
- package/dist/components/atoms/Button.svelte +111 -3
- package/dist/components/atoms/Button.svelte.d.ts +4 -0
- package/dist/components/layouts/NavBar.svelte +184 -30
- package/dist/components/layouts/NavBar.svelte.d.ts +19 -3
- package/dist/components/molecules/GlyphStack.svelte +12 -19
- package/dist/components/molecules/RadialMenu.svelte +579 -0
- package/dist/components/molecules/RadialMenu.svelte.d.ts +42 -0
- package/dist/components/molecules/SegmentedControl.svelte +6 -3
- package/dist/components/molecules/SegmentedControl.svelte.d.ts +4 -2
- package/dist/components/molecules/SortableList.svelte +720 -0
- package/dist/components/molecules/SortableList.svelte.d.ts +116 -0
- package/dist/components/molecules/segmented-control-count.d.ts +2 -0
- package/dist/components/molecules/segmented-control-count.js +6 -0
- package/dist/components/molecules/sortable-list-order.d.ts +68 -0
- package/dist/components/molecules/sortable-list-order.js +133 -0
- package/dist/floating.d.ts +38 -0
- package/dist/floating.js +38 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/slide-select.d.ts +26 -0
- package/dist/slide-select.js +53 -0
- package/package.json +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export interface SortableListMove {
|
|
2
|
+
key: string;
|
|
3
|
+
from: number;
|
|
4
|
+
to: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SortableListRowContext {
|
|
7
|
+
index: number;
|
|
8
|
+
grabbed: boolean;
|
|
9
|
+
selected: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Accessible strings. `pos` is 1-based. */
|
|
12
|
+
export interface SortableListMessages {
|
|
13
|
+
instructions: string;
|
|
14
|
+
reorder: (name: string) => string;
|
|
15
|
+
moveUp: (name: string) => string;
|
|
16
|
+
moveDown: (name: string) => string;
|
|
17
|
+
grabbed: (name: string, pos: number, total: number) => string;
|
|
18
|
+
moved: (name: string, pos: number, total: number) => string;
|
|
19
|
+
dropped: (name: string, pos: number, total: number) => string;
|
|
20
|
+
cancelled: (name: string, pos: number, total: number) => string;
|
|
21
|
+
}
|
|
22
|
+
export declare const SORTABLE_LIST_MESSAGES: SortableListMessages;
|
|
23
|
+
/**
|
|
24
|
+
* Reorderable list. The consumer owns the order: `onreorder` reports the new
|
|
25
|
+
* key list, the list shows it until `items` changes or `revertAfter` ms pass.
|
|
26
|
+
* Drag runs on Pointer Events (touch included); the grip is also a keyboard
|
|
27
|
+
* grab button and the arrows move one step.
|
|
28
|
+
*/
|
|
29
|
+
import { type Snippet } from 'svelte';
|
|
30
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
31
|
+
declare function $$render<T>(): {
|
|
32
|
+
props: Omit<HTMLAttributes<HTMLDivElement>, keyof {
|
|
33
|
+
items: T[];
|
|
34
|
+
key: (item: T) => string;
|
|
35
|
+
/** Row content; the kit renders the grip, ordinal and arrows around it. */
|
|
36
|
+
row: Snippet<[T, SortableListRowContext]>;
|
|
37
|
+
/** Interactive row extras (go, skip), kept outside the selectable body. */
|
|
38
|
+
trailing?: Snippet<[T, SortableListRowContext]>;
|
|
39
|
+
/** Fires on drop or an arrow press with the full new key order. */
|
|
40
|
+
onreorder?: (keys: string[], move: SortableListMove) => void;
|
|
41
|
+
/** Veto a target index for a key. */
|
|
42
|
+
canMove?: (key: string, to: number) => boolean;
|
|
43
|
+
/** A locked row has no grip and disabled arrows. */
|
|
44
|
+
locked?: (item: T) => boolean;
|
|
45
|
+
/** Accessible row name used in labels and announcements. Defaults to the key. */
|
|
46
|
+
name?: (item: T) => string;
|
|
47
|
+
arrows?: boolean;
|
|
48
|
+
numbered?: boolean;
|
|
49
|
+
selected?: string;
|
|
50
|
+
onselect?: (key: string) => void;
|
|
51
|
+
/** Render the row body as a select button. Defaults to on when `onselect` or `selected` is given. */
|
|
52
|
+
selectable?: boolean;
|
|
53
|
+
/** `row` drags from anywhere on the row; touch then needs a 250 ms long-press. */
|
|
54
|
+
handle?: "grip" | "row";
|
|
55
|
+
size?: "sm" | "md";
|
|
56
|
+
label: string;
|
|
57
|
+
messages?: Partial<SortableListMessages>;
|
|
58
|
+
as?: "ol" | "ul";
|
|
59
|
+
/** Ms the optimistic order is kept when `items` is not updated. */
|
|
60
|
+
revertAfter?: number;
|
|
61
|
+
class?: string;
|
|
62
|
+
style?: string;
|
|
63
|
+
}> & {
|
|
64
|
+
items: T[];
|
|
65
|
+
key: (item: T) => string;
|
|
66
|
+
/** Row content; the kit renders the grip, ordinal and arrows around it. */
|
|
67
|
+
row: Snippet<[T, SortableListRowContext]>;
|
|
68
|
+
/** Interactive row extras (go, skip), kept outside the selectable body. */
|
|
69
|
+
trailing?: Snippet<[T, SortableListRowContext]>;
|
|
70
|
+
/** Fires on drop or an arrow press with the full new key order. */
|
|
71
|
+
onreorder?: (keys: string[], move: SortableListMove) => void;
|
|
72
|
+
/** Veto a target index for a key. */
|
|
73
|
+
canMove?: (key: string, to: number) => boolean;
|
|
74
|
+
/** A locked row has no grip and disabled arrows. */
|
|
75
|
+
locked?: (item: T) => boolean;
|
|
76
|
+
/** Accessible row name used in labels and announcements. Defaults to the key. */
|
|
77
|
+
name?: (item: T) => string;
|
|
78
|
+
arrows?: boolean;
|
|
79
|
+
numbered?: boolean;
|
|
80
|
+
selected?: string;
|
|
81
|
+
onselect?: (key: string) => void;
|
|
82
|
+
/** Render the row body as a select button. Defaults to on when `onselect` or `selected` is given. */
|
|
83
|
+
selectable?: boolean;
|
|
84
|
+
/** `row` drags from anywhere on the row; touch then needs a 250 ms long-press. */
|
|
85
|
+
handle?: "grip" | "row";
|
|
86
|
+
size?: "sm" | "md";
|
|
87
|
+
label: string;
|
|
88
|
+
messages?: Partial<SortableListMessages>;
|
|
89
|
+
as?: "ol" | "ul";
|
|
90
|
+
/** Ms the optimistic order is kept when `items` is not updated. */
|
|
91
|
+
revertAfter?: number;
|
|
92
|
+
class?: string;
|
|
93
|
+
style?: string;
|
|
94
|
+
};
|
|
95
|
+
exports: {};
|
|
96
|
+
bindings: "selected";
|
|
97
|
+
slots: {};
|
|
98
|
+
events: {};
|
|
99
|
+
};
|
|
100
|
+
declare class __sveltets_Render<T> {
|
|
101
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
102
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
103
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
104
|
+
bindings(): "selected";
|
|
105
|
+
exports(): {};
|
|
106
|
+
}
|
|
107
|
+
interface $$IsomorphicComponent {
|
|
108
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
109
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
110
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
111
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
112
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
113
|
+
}
|
|
114
|
+
declare const SortableList: $$IsomorphicComponent;
|
|
115
|
+
type SortableList<T> = InstanceType<typeof SortableList<T>>;
|
|
116
|
+
export default SortableList;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move the entry at `from` to `to`, returning a new array.
|
|
3
|
+
*
|
|
4
|
+
* @template T
|
|
5
|
+
* @param {T[]} list
|
|
6
|
+
* @param {number} from
|
|
7
|
+
* @param {number} to
|
|
8
|
+
* @returns {T[]}
|
|
9
|
+
*/
|
|
10
|
+
export function moveItem<T>(list: T[], from: number, to: number): T[];
|
|
11
|
+
/**
|
|
12
|
+
* True when both key lists hold the same keys, in any order.
|
|
13
|
+
*
|
|
14
|
+
* @param {string[]} a
|
|
15
|
+
* @param {string[]} b
|
|
16
|
+
*/
|
|
17
|
+
export function sameKeys(a: string[], b: string[]): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The nearest index from `from` in direction `dir` that `allowed` accepts,
|
|
20
|
+
* skipping vetoed slots. `undefined` when nothing in that direction is allowed.
|
|
21
|
+
*
|
|
22
|
+
* @param {number} from
|
|
23
|
+
* @param {number} count
|
|
24
|
+
* @param {1 | -1} dir
|
|
25
|
+
* @param {(to: number) => boolean} allowed
|
|
26
|
+
* @returns {number | undefined}
|
|
27
|
+
*/
|
|
28
|
+
export function stepTarget(from: number, count: number, dir: 1 | -1, allowed: (to: number) => boolean): number | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The farthest index toward `edge` that `allowed` accepts.
|
|
31
|
+
*
|
|
32
|
+
* @param {number} from
|
|
33
|
+
* @param {number} count
|
|
34
|
+
* @param {'start' | 'end'} edge
|
|
35
|
+
* @param {(to: number) => boolean} allowed
|
|
36
|
+
* @returns {number | undefined}
|
|
37
|
+
*/
|
|
38
|
+
export function edgeTarget(from: number, count: number, edge: "start" | "end", allowed: (to: number) => boolean): number | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a key pressed on a grabbed grip to a list action.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} key
|
|
43
|
+
* @returns {'up' | 'down' | 'start' | 'end' | 'drop' | 'cancel' | undefined}
|
|
44
|
+
*/
|
|
45
|
+
export function grabbedAction(key: string): "up" | "down" | "start" | "end" | "drop" | "cancel" | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Slot a dragged row lands in: the one whose top is nearest the dragged row's
|
|
48
|
+
* top. `heights` are the other rows, top to bottom; `top` is relative to the
|
|
49
|
+
* first slot.
|
|
50
|
+
*
|
|
51
|
+
* @param {number} top
|
|
52
|
+
* @param {number[]} heights
|
|
53
|
+
* @returns {number}
|
|
54
|
+
*/
|
|
55
|
+
export function slotForTop(top: number, heights: number[]): number;
|
|
56
|
+
/**
|
|
57
|
+
* Auto-scroll velocity for a pointer at `y` inside a box spanning
|
|
58
|
+
* `top`..`bottom`: negative near the top edge, positive near the bottom,
|
|
59
|
+
* scaled by how deep into the `zone` the pointer is.
|
|
60
|
+
*
|
|
61
|
+
* @param {number} y
|
|
62
|
+
* @param {number} top
|
|
63
|
+
* @param {number} bottom
|
|
64
|
+
* @param {number} [zone]
|
|
65
|
+
* @param {number} [max] px per frame
|
|
66
|
+
* @returns {number}
|
|
67
|
+
*/
|
|
68
|
+
export function edgeScroll(y: number, top: number, bottom: number, zone?: number, max?: number): number;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move the entry at `from` to `to`, returning a new array.
|
|
3
|
+
*
|
|
4
|
+
* @template T
|
|
5
|
+
* @param {T[]} list
|
|
6
|
+
* @param {number} from
|
|
7
|
+
* @param {number} to
|
|
8
|
+
* @returns {T[]}
|
|
9
|
+
*/
|
|
10
|
+
export function moveItem(list, from, to) {
|
|
11
|
+
const next = [...list];
|
|
12
|
+
if (from < 0 || from >= next.length) return next;
|
|
13
|
+
const clamped = Math.min(Math.max(to, 0), next.length - 1);
|
|
14
|
+
const [moved] = next.splice(from, 1);
|
|
15
|
+
next.splice(clamped, 0, moved);
|
|
16
|
+
return next;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* True when both key lists hold the same keys, in any order.
|
|
21
|
+
*
|
|
22
|
+
* @param {string[]} a
|
|
23
|
+
* @param {string[]} b
|
|
24
|
+
*/
|
|
25
|
+
export function sameKeys(a, b) {
|
|
26
|
+
if (a.length !== b.length) return false;
|
|
27
|
+
const set = new Set(a);
|
|
28
|
+
return b.every((k) => set.has(k));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The nearest index from `from` in direction `dir` that `allowed` accepts,
|
|
33
|
+
* skipping vetoed slots. `undefined` when nothing in that direction is allowed.
|
|
34
|
+
*
|
|
35
|
+
* @param {number} from
|
|
36
|
+
* @param {number} count
|
|
37
|
+
* @param {1 | -1} dir
|
|
38
|
+
* @param {(to: number) => boolean} allowed
|
|
39
|
+
* @returns {number | undefined}
|
|
40
|
+
*/
|
|
41
|
+
export function stepTarget(from, count, dir, allowed) {
|
|
42
|
+
for (let to = from + dir; to >= 0 && to < count; to += dir) {
|
|
43
|
+
if (allowed(to)) return to;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The farthest index toward `edge` that `allowed` accepts.
|
|
50
|
+
*
|
|
51
|
+
* @param {number} from
|
|
52
|
+
* @param {number} count
|
|
53
|
+
* @param {'start' | 'end'} edge
|
|
54
|
+
* @param {(to: number) => boolean} allowed
|
|
55
|
+
* @returns {number | undefined}
|
|
56
|
+
*/
|
|
57
|
+
export function edgeTarget(from, count, edge, allowed) {
|
|
58
|
+
if (edge === 'start') {
|
|
59
|
+
for (let to = 0; to < from; to += 1) if (allowed(to)) return to;
|
|
60
|
+
} else {
|
|
61
|
+
for (let to = count - 1; to > from; to -= 1) if (allowed(to)) return to;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve a key pressed on a grabbed grip to a list action.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} key
|
|
70
|
+
* @returns {'up' | 'down' | 'start' | 'end' | 'drop' | 'cancel' | undefined}
|
|
71
|
+
*/
|
|
72
|
+
export function grabbedAction(key) {
|
|
73
|
+
switch (key) {
|
|
74
|
+
case 'ArrowUp':
|
|
75
|
+
return 'up';
|
|
76
|
+
case 'ArrowDown':
|
|
77
|
+
return 'down';
|
|
78
|
+
case 'Home':
|
|
79
|
+
return 'start';
|
|
80
|
+
case 'End':
|
|
81
|
+
return 'end';
|
|
82
|
+
case ' ':
|
|
83
|
+
case 'Enter':
|
|
84
|
+
return 'drop';
|
|
85
|
+
case 'Escape':
|
|
86
|
+
return 'cancel';
|
|
87
|
+
default:
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Slot a dragged row lands in: the one whose top is nearest the dragged row's
|
|
94
|
+
* top. `heights` are the other rows, top to bottom; `top` is relative to the
|
|
95
|
+
* first slot.
|
|
96
|
+
*
|
|
97
|
+
* @param {number} top
|
|
98
|
+
* @param {number[]} heights
|
|
99
|
+
* @returns {number}
|
|
100
|
+
*/
|
|
101
|
+
export function slotForTop(top, heights) {
|
|
102
|
+
let best = 0;
|
|
103
|
+
let bestDist = Math.abs(top);
|
|
104
|
+
let slotTop = 0;
|
|
105
|
+
for (let j = 0; j < heights.length; j += 1) {
|
|
106
|
+
slotTop += heights[j];
|
|
107
|
+
const dist = Math.abs(top - slotTop);
|
|
108
|
+
if (dist < bestDist) {
|
|
109
|
+
best = j + 1;
|
|
110
|
+
bestDist = dist;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return best;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Auto-scroll velocity for a pointer at `y` inside a box spanning
|
|
118
|
+
* `top`..`bottom`: negative near the top edge, positive near the bottom,
|
|
119
|
+
* scaled by how deep into the `zone` the pointer is.
|
|
120
|
+
*
|
|
121
|
+
* @param {number} y
|
|
122
|
+
* @param {number} top
|
|
123
|
+
* @param {number} bottom
|
|
124
|
+
* @param {number} [zone]
|
|
125
|
+
* @param {number} [max] px per frame
|
|
126
|
+
* @returns {number}
|
|
127
|
+
*/
|
|
128
|
+
export function edgeScroll(y, top, bottom, zone = 40, max = 12) {
|
|
129
|
+
if (bottom - top <= zone * 2) return 0;
|
|
130
|
+
if (y < top + zone) return -Math.ceil(max * Math.min(1, (top + zone - y) / zone));
|
|
131
|
+
if (y > bottom - zone) return Math.ceil(max * Math.min(1, (y - (bottom - zone)) / zone));
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
package/dist/floating.d.ts
CHANGED
|
@@ -12,3 +12,41 @@ export type AnchorRect = Pick<DOMRect, 'top' | 'left' | 'bottom' | 'right' | 'wi
|
|
|
12
12
|
export declare function place(trigger: HTMLElement, floating: HTMLElement, placement?: Placement, gap?: number): void;
|
|
13
13
|
/** `place` against a viewport rect instead of an element. */
|
|
14
14
|
export declare function placeAt(t: AnchorRect, floating: HTMLElement, placement?: Placement, gap?: number): void;
|
|
15
|
+
/** Viewport edges (px) that a floating layout must stay inside. */
|
|
16
|
+
export interface Bounds {
|
|
17
|
+
left: number;
|
|
18
|
+
top: number;
|
|
19
|
+
right: number;
|
|
20
|
+
bottom: number;
|
|
21
|
+
}
|
|
22
|
+
export interface Point {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
}
|
|
26
|
+
export interface FanInput {
|
|
27
|
+
/** Centre of the trigger, in viewport px. */
|
|
28
|
+
center: Point;
|
|
29
|
+
count: number;
|
|
30
|
+
/** Degrees, 0 = right, counter-clockwise. */
|
|
31
|
+
from: number;
|
|
32
|
+
to: number;
|
|
33
|
+
/** Preferred distance from `center` to each petal centre. */
|
|
34
|
+
radius: number;
|
|
35
|
+
/** Petal diameter. */
|
|
36
|
+
petal: number;
|
|
37
|
+
/** Smallest radius the fan may shrink to before it is shifted instead. */
|
|
38
|
+
minRadius?: number;
|
|
39
|
+
bounds: Bounds;
|
|
40
|
+
}
|
|
41
|
+
export interface FanLayout {
|
|
42
|
+
radius: number;
|
|
43
|
+
/** Petal centres, in `count` order. */
|
|
44
|
+
points: Point[];
|
|
45
|
+
}
|
|
46
|
+
/** Angles (degrees) of `count` petals spread evenly from `from` to `to`. */
|
|
47
|
+
export declare function fanAngles(count: number, from: number, to: number): number[];
|
|
48
|
+
/**
|
|
49
|
+
* Lay `count` petals on an arc around `center`, shrinking the radius until every
|
|
50
|
+
* petal fits in `bounds`; if even `minRadius` overflows, the whole fan shifts in.
|
|
51
|
+
*/
|
|
52
|
+
export declare function fanLayout({ center, count, from, to, radius, petal, minRadius, bounds, }: FanInput): FanLayout;
|
package/dist/floating.js
CHANGED
|
@@ -57,3 +57,41 @@ export function placeAt(t, floating, placement = 'bottom-start', gap = 6) {
|
|
|
57
57
|
floating.style.top = `${Math.round(top)}px`;
|
|
58
58
|
floating.style.left = `${Math.round(left)}px`;
|
|
59
59
|
}
|
|
60
|
+
/** Angles (degrees) of `count` petals spread evenly from `from` to `to`. */
|
|
61
|
+
export function fanAngles(count, from, to) {
|
|
62
|
+
if (count <= 1)
|
|
63
|
+
return count === 1 ? [(from + to) / 2] : [];
|
|
64
|
+
return Array.from({ length: count }, (_, i) => from + ((to - from) * i) / (count - 1));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Lay `count` petals on an arc around `center`, shrinking the radius until every
|
|
68
|
+
* petal fits in `bounds`; if even `minRadius` overflows, the whole fan shifts in.
|
|
69
|
+
*/
|
|
70
|
+
export function fanLayout({ center, count, from, to, radius, petal, minRadius = 0, bounds, }) {
|
|
71
|
+
const half = petal / 2;
|
|
72
|
+
const lo = { x: bounds.left + half, y: bounds.top + half };
|
|
73
|
+
const hi = { x: bounds.right - half, y: bounds.bottom - half };
|
|
74
|
+
const units = fanAngles(count, from, to).map((deg) => {
|
|
75
|
+
const rad = (deg * Math.PI) / 180;
|
|
76
|
+
return { x: Math.cos(rad), y: -Math.sin(rad) };
|
|
77
|
+
});
|
|
78
|
+
const reach = (c, u, min, max) => u > 1e-9 ? (max - c) / u : u < -1e-9 ? (min - c) / u : Number.POSITIVE_INFINITY;
|
|
79
|
+
let r = radius;
|
|
80
|
+
for (const u of units) {
|
|
81
|
+
r = Math.min(r, reach(center.x, u.x, lo.x, hi.x), reach(center.y, u.y, lo.y, hi.y));
|
|
82
|
+
}
|
|
83
|
+
r = Math.max(r, Math.min(minRadius, radius));
|
|
84
|
+
const raw = units.map((u) => ({ x: center.x + u.x * r, y: center.y + u.y * r }));
|
|
85
|
+
const shift = (vals, min, max) => {
|
|
86
|
+
if (!vals.length)
|
|
87
|
+
return 0;
|
|
88
|
+
const a = Math.min(...vals);
|
|
89
|
+
const b = Math.max(...vals);
|
|
90
|
+
if (b - a > max - min)
|
|
91
|
+
return (min + max) / 2 - (a + b) / 2;
|
|
92
|
+
return a < min ? min - a : b > max ? max - b : 0;
|
|
93
|
+
};
|
|
94
|
+
const dx = shift(raw.map((p) => p.x), lo.x, hi.x);
|
|
95
|
+
const dy = shift(raw.map((p) => p.y), lo.y, hi.y);
|
|
96
|
+
return { radius: r, points: raw.map((p) => ({ x: p.x + dx, y: p.y + dy })) };
|
|
97
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -79,10 +79,12 @@ export { default as OptionButton } from './components/molecules/OptionButton.sve
|
|
|
79
79
|
export { default as Pagination } from './components/molecules/Pagination.svelte';
|
|
80
80
|
export { default as Popover } from './components/molecules/Popover.svelte';
|
|
81
81
|
export { default as Prose } from './components/molecules/Prose.svelte';
|
|
82
|
+
export { default as RadialMenu, type RadialArc, } from './components/molecules/RadialMenu.svelte';
|
|
82
83
|
export { default as RadioGroup, type RadioOption, } from './components/molecules/RadioGroup.svelte';
|
|
83
84
|
export { default as SectionHeader } from './components/molecules/SectionHeader.svelte';
|
|
84
85
|
export { default as SegmentedControl, type SegmentOption, } from './components/molecules/SegmentedControl.svelte';
|
|
85
86
|
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
87
|
+
export { default as SortableList, SORTABLE_LIST_MESSAGES, type SortableListMessages, type SortableListMove, type SortableListRowContext, } from './components/molecules/SortableList.svelte';
|
|
86
88
|
export { default as SplitButton } from './components/molecules/SplitButton.svelte';
|
|
87
89
|
export { default as StatusBar } from './components/molecules/StatusBar.svelte';
|
|
88
90
|
export { default as Tabs, type TabItem } from './components/molecules/Tabs.svelte';
|
package/dist/index.js
CHANGED
|
@@ -87,10 +87,12 @@ export { default as OptionButton } from './components/molecules/OptionButton.sve
|
|
|
87
87
|
export { default as Pagination } from './components/molecules/Pagination.svelte';
|
|
88
88
|
export { default as Popover } from './components/molecules/Popover.svelte';
|
|
89
89
|
export { default as Prose } from './components/molecules/Prose.svelte';
|
|
90
|
+
export { default as RadialMenu, } from './components/molecules/RadialMenu.svelte';
|
|
90
91
|
export { default as RadioGroup, } from './components/molecules/RadioGroup.svelte';
|
|
91
92
|
export { default as SectionHeader } from './components/molecules/SectionHeader.svelte';
|
|
92
93
|
export { default as SegmentedControl, } from './components/molecules/SegmentedControl.svelte';
|
|
93
94
|
export { default as SelectButton } from './components/molecules/SelectButton.svelte';
|
|
95
|
+
export { default as SortableList, SORTABLE_LIST_MESSAGES, } from './components/molecules/SortableList.svelte';
|
|
94
96
|
export { default as SplitButton } from './components/molecules/SplitButton.svelte';
|
|
95
97
|
export { default as StatusBar } from './components/molecules/StatusBar.svelte';
|
|
96
98
|
export { default as Tabs } from './components/molecules/Tabs.svelte';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const SLIDE_THRESHOLD = 12;
|
|
2
|
+
export interface SlideMove {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
dx: number;
|
|
6
|
+
dy: number;
|
|
7
|
+
}
|
|
8
|
+
export interface SlideOptions {
|
|
9
|
+
/** Target index under the pointer, or -1. Not asked within `threshold` of the press. */
|
|
10
|
+
pick: (move: SlideMove) => number;
|
|
11
|
+
/** The armed target changed (-1: none). */
|
|
12
|
+
onarm: (index: number) => void;
|
|
13
|
+
threshold?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface SlideSelect {
|
|
16
|
+
/** Start tracking; captures the pointer on `el` so moves keep arriving over a top-layer panel. */
|
|
17
|
+
down(e: PointerEvent, el?: Element | null): void;
|
|
18
|
+
move(e: PointerEvent): void;
|
|
19
|
+
/** Stop tracking; returns the armed index (or -1). */
|
|
20
|
+
up(): number;
|
|
21
|
+
cancel(): void;
|
|
22
|
+
readonly active: boolean;
|
|
23
|
+
/** True once the pointer travelled past the threshold since `down`. */
|
|
24
|
+
readonly slid: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function slideSelect({ pick, onarm, threshold, }: SlideOptions): SlideSelect;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Press-and-slide picking: press a trigger, drag over a target to arm it,
|
|
2
|
+
// release to fire it. The caller owns opening and maps a pointer to a target.
|
|
3
|
+
export const SLIDE_THRESHOLD = 12;
|
|
4
|
+
export function slideSelect({ pick, onarm, threshold = SLIDE_THRESHOLD, }) {
|
|
5
|
+
let origin = null;
|
|
6
|
+
let armed = -1;
|
|
7
|
+
let slid = false;
|
|
8
|
+
const arm = (i) => {
|
|
9
|
+
if (i === armed)
|
|
10
|
+
return;
|
|
11
|
+
armed = i;
|
|
12
|
+
onarm(i);
|
|
13
|
+
};
|
|
14
|
+
const stop = () => {
|
|
15
|
+
const i = armed;
|
|
16
|
+
origin = null;
|
|
17
|
+
arm(-1);
|
|
18
|
+
return i;
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
down(e, el) {
|
|
22
|
+
origin = { x: e.clientX, y: e.clientY };
|
|
23
|
+
slid = false;
|
|
24
|
+
arm(-1);
|
|
25
|
+
try {
|
|
26
|
+
el?.setPointerCapture(e.pointerId);
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
},
|
|
30
|
+
move(e) {
|
|
31
|
+
if (!origin)
|
|
32
|
+
return;
|
|
33
|
+
const dx = e.clientX - origin.x;
|
|
34
|
+
const dy = e.clientY - origin.y;
|
|
35
|
+
if (Math.hypot(dx, dy) < threshold) {
|
|
36
|
+
arm(-1);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
slid = true;
|
|
40
|
+
arm(pick({ x: e.clientX, y: e.clientY, dx, dy }));
|
|
41
|
+
},
|
|
42
|
+
up: () => (origin ? stop() : -1),
|
|
43
|
+
cancel() {
|
|
44
|
+
stop();
|
|
45
|
+
},
|
|
46
|
+
get active() {
|
|
47
|
+
return origin !== null;
|
|
48
|
+
},
|
|
49
|
+
get slid() {
|
|
50
|
+
return slid;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
package/package.json
CHANGED