@spotlab/hand-matrix 0.1.0 → 0.2.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/README.md +88 -4
- package/dist/editor/use-range-painting.d.ts +7 -3
- package/dist/hand-matrix.d.ts +26 -6
- package/dist/index.d.ts +6 -11
- package/dist/index.js +200 -173
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -16,20 +16,104 @@ await initRange(); // once, at startup
|
|
|
16
16
|
|
|
17
17
|
## Exports
|
|
18
18
|
|
|
19
|
+
Everything below is exported from the package root.
|
|
20
|
+
|
|
21
|
+
### Display
|
|
22
|
+
|
|
19
23
|
- `HandMatrix` — the bare 13x13 grid primitive. Takes no range data; styling
|
|
20
24
|
is entirely the caller's job via `comboStyle`, keyed by combo string (e.g.
|
|
21
|
-
`"AKs"`).
|
|
25
|
+
`"AKs"`). Each cell carries a `data-combo` attribute. Props:
|
|
26
|
+
- `comboStyle?: (combo) => CSSProperties` — per-cell style.
|
|
27
|
+
- `onPointerDown?`, `onPointerEnter?: CellPointerHandler` — the
|
|
28
|
+
`(combo, event)` handlers to use for dragging across cells, with a mouse
|
|
29
|
+
or a finger. `onPointerDown` releases the implicit pointer capture a
|
|
30
|
+
touch takes on its first cell. `onPointerEnter` fires once each time the
|
|
31
|
+
pointer moves onto a new cell, found by hit-testing each `pointermove`
|
|
32
|
+
(`elementFromPoint`), so it also fires during a touch drag, where
|
|
33
|
+
enter events never reach the other cells. Passing `onPointerDown` sets
|
|
34
|
+
`touch-action: none` and `user-select: none` on the grid, so a drag
|
|
35
|
+
neither scrolls the page nor selects the combo text it crosses.
|
|
36
|
+
- `className?: string` — merged onto the `.spotlab-hm-root` wrapper.
|
|
22
37
|
- `RangeGrid` — a read-only display component. Accepts the expanded
|
|
23
38
|
`HandFrequency[]` form or a range string directly (parsed via
|
|
24
|
-
`@spotlab/range`), and fills each cell to its combo's mean weight.
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
`@spotlab/range`), and fills each cell to its combo's mean weight. Props:
|
|
40
|
+
`range: HandFrequency[] | string`, `fill?: string` (a CSS color, defaulting
|
|
41
|
+
to `var(--spotlab-hm-fill)`), `className?: string`.
|
|
42
|
+
- `cellFillGradient(stops: string[]): string` / `CELL_FILL_DIRECTION` — the
|
|
43
|
+
canonical left-to-right gradient helper used to render a cell's fill.
|
|
44
|
+
Returns `"transparent"` for no stops.
|
|
27
45
|
|
|
28
46
|
`RangeGrid`'s combo collapse (`toComboWeights`) is a wasm call, so
|
|
29
47
|
`initRange()` must have resolved before rendering it — see
|
|
30
48
|
[`@spotlab/range`](../range)'s own README for why that call is explicit
|
|
31
49
|
rather than automatic.
|
|
32
50
|
|
|
51
|
+
### Editing
|
|
52
|
+
|
|
53
|
+
The editor splits a parent range across a set of actions (e.g. fold, call,
|
|
54
|
+
raise). Its state is an array of 169 `EditorCell`s, one per combo in
|
|
55
|
+
`@spotlab/range`'s `COMBOS` order. Every component is fully controlled: state
|
|
56
|
+
goes in as props and changes come out through `on*Change` callbacks.
|
|
57
|
+
|
|
58
|
+
- `RangeMatrixEditor` — the paintable grid. Draws each cell's action bands,
|
|
59
|
+
hatches dead cells (`avail` 0), marks capped cells (`avail` below 1), and
|
|
60
|
+
wires brush/eyedropper painting onto `HandMatrix` for mouse and touch.
|
|
61
|
+
Props (`RangeMatrixEditorProps`):
|
|
62
|
+
- `cells: EditorCell[]` and `onCellsChange(cells)`.
|
|
63
|
+
- `actions: EditorActionSpec[]` — index-aligned with each cell's
|
|
64
|
+
`weights`.
|
|
65
|
+
- `order: number[]` — indices into `actions`, in the order bands stack.
|
|
66
|
+
- `brush: number[]` and `onBrushChange(brush)` — the weights a paint
|
|
67
|
+
stroke applies, index-aligned with `actions`.
|
|
68
|
+
- `tool: PaintTool` and `onToolChange(tool)` — `"brush"` paints on press
|
|
69
|
+
and drag; `"eyedropper"` loads a cell's weights onto the brush and
|
|
70
|
+
switches back to `"brush"`.
|
|
71
|
+
- `className?: string`.
|
|
72
|
+
- `ActionWeightSliders` — one slider and numeric input per action for
|
|
73
|
+
editing a brush (or any weight row). Moving one action rebalances the
|
|
74
|
+
others so the row keeps summing to 100. Props (`ActionWeightSlidersProps`):
|
|
75
|
+
- `actions`, `order`, `weights: number[]` and `onWeightsChange(weights)`.
|
|
76
|
+
- `passive: number` — the index of the remainder action, which absorbs
|
|
77
|
+
whatever the others leave.
|
|
78
|
+
- `renderActionLabel?(action, index, isPassive)` — replaces a row's
|
|
79
|
+
default label.
|
|
80
|
+
- `renderActionExtra?(action, index)` — content after the label (e.g. a
|
|
81
|
+
remove button).
|
|
82
|
+
- `components?: { WeightInput?: ComponentType<WeightInputProps> }` — swap
|
|
83
|
+
the default `<input type="number">`.
|
|
84
|
+
- `className?: string`.
|
|
85
|
+
- `useRangePainting(opts)` — the brush/eyedropper state machine behind
|
|
86
|
+
`RangeMatrixEditor`, for building your own grid. `opts` takes the editor's
|
|
87
|
+
`cells`, `brush`, `tool` and their three `on*Change` callbacks. Returns
|
|
88
|
+
`{ onCellDown(combo), onCellEnter(combo) }`; wire them to `HandMatrix`'s
|
|
89
|
+
`onPointerDown`/`onPointerEnter`. A stroke ends on any window `pointerup`
|
|
90
|
+
or `pointercancel`.
|
|
91
|
+
- `seedCells(avail, actionWeights, passive): EditorCell[]` — build the
|
|
92
|
+
initial grid from a parent range's per-combo availability (`null` for
|
|
93
|
+
unconstrained) and each action's absolute per-combo weights (the
|
|
94
|
+
`Map<string, number>` that `toComboWeights` from `@spotlab/range`
|
|
95
|
+
produces).
|
|
96
|
+
- `cellsToComboWeights(cells, actionIndex): Map<string, number>` — collapse
|
|
97
|
+
one action back to absolute per-combo weights, ready to save as a range.
|
|
98
|
+
- `comboWeightsToCells(cells, actionWeights, passive): EditorCell[]` — the
|
|
99
|
+
inverse: re-derive the cells' conditional weights from absolute
|
|
100
|
+
per-action ranges, keeping each cell's `avail`.
|
|
101
|
+
- `rebalance(weights, index, value, passive): number[]` — set one action's
|
|
102
|
+
weight and rescale the rest so the row sums to 100. This is what
|
|
103
|
+
`ActionWeightSliders` uses.
|
|
104
|
+
- Types: `EditorCell` (`{ avail: number; weights: number[] }`, where `avail`
|
|
105
|
+
is the combo's cap in the parent range and `weights` are each action's
|
|
106
|
+
share of it, summing to 100), `EditorActionSpec`
|
|
107
|
+
(`{ id: string; label: string; color: string }`), `PaintTool`,
|
|
108
|
+
`CellPointerHandler`, `RangeMatrixEditorProps`, `ActionWeightSlidersProps`,
|
|
109
|
+
`WeightInputProps`.
|
|
110
|
+
|
|
111
|
+
**The editor works at combo level.** A cell is one of the 169 combos (`AKs`,
|
|
112
|
+
`AKo`, `QQ`), so suit-specific hands such as `AsKd` don't survive a round
|
|
113
|
+
trip: `seedCells` takes combo weights, and `cellsToComboWeights` returns
|
|
114
|
+
them. To keep suit-specific hands, set them aside before editing and add them
|
|
115
|
+
back to the result yourself.
|
|
116
|
+
|
|
33
117
|
## Theming
|
|
34
118
|
|
|
35
119
|
The package ships `hand-matrix.css` (`@spotlab/hand-matrix/style.css`),
|
|
@@ -2,9 +2,13 @@ import { EditorCell } from './cells';
|
|
|
2
2
|
export type PaintTool = "brush" | "eyedropper";
|
|
3
3
|
/**
|
|
4
4
|
* The brush/eyedropper state machine behind `RangeMatrixEditor`: drag-painting
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* and eyedropping a cell's weights back onto the brush. Fully controlled — all
|
|
6
|
+
* state changes go out through the `on*Change` callbacks.
|
|
7
|
+
*
|
|
8
|
+
* Wire `onCellDown` to a cell's pointer-down and `onCellEnter` to the pointer
|
|
9
|
+
* moving onto a cell (`HandMatrix`'s `onPointerDown`/`onPointerEnter` do both,
|
|
10
|
+
* for mouse and touch alike). A stroke ends on any window `pointerup` or
|
|
11
|
+
* `pointercancel`.
|
|
8
12
|
*/
|
|
9
13
|
export declare function useRangePainting(opts: {
|
|
10
14
|
cells: EditorCell[];
|
package/dist/hand-matrix.d.ts
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
|
-
import { CSSProperties, FC } from 'react';
|
|
1
|
+
import { CSSProperties, FC, PointerEvent as ReactPointerEvent } from 'react';
|
|
2
2
|
export declare const CELL_FILL_DIRECTION = "to right";
|
|
3
3
|
export declare const cellFillGradient: (stops: string[]) => string;
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
onMouseDown?: (combo: string) => void;
|
|
7
|
-
onMouseEnter?: (combo: string) => void;
|
|
4
|
+
export type CellPointerHandler = (combo: string, event: ReactPointerEvent<HTMLDivElement>) => void;
|
|
5
|
+
type ComboStyleProp = {
|
|
8
6
|
comboStyle?: (combo: string) => CSSProperties;
|
|
9
7
|
};
|
|
8
|
+
type GridPointerHandlers = {
|
|
9
|
+
/**
|
|
10
|
+
* A pointer (mouse, pen or touch) went down on `combo`'s cell. The grid
|
|
11
|
+
* first releases the implicit pointer capture a touch takes on the element
|
|
12
|
+
* it starts on, so later moves can resolve to other cells. Passing this
|
|
13
|
+
* also makes the grid root a drag surface (see `dragSurfaceStyle`).
|
|
14
|
+
*/
|
|
15
|
+
onPointerDown?: CellPointerHandler;
|
|
16
|
+
/**
|
|
17
|
+
* The pointer moved onto `combo`'s cell. Resolved by hit-testing each
|
|
18
|
+
* `pointermove` (`elementFromPoint`) rather than by enter events, so it
|
|
19
|
+
* fires across a touch drag, where `mouseenter`/`pointerenter` never reach
|
|
20
|
+
* the other cells, as well as for a mouse. Fires once per change of cell,
|
|
21
|
+
* whether or not a button is pressed.
|
|
22
|
+
*/
|
|
23
|
+
onPointerEnter?: CellPointerHandler;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The bare 13x13 grid. Each cell carries a `data-combo` attribute. Passing
|
|
27
|
+
* `onPointerDown` marks the grid as a drag surface (`dragSurfaceStyle`): a
|
|
28
|
+
* drag reaches the handlers instead of scrolling the page or selecting text.
|
|
29
|
+
*/
|
|
10
30
|
export declare const HandMatrix: FC<{
|
|
11
31
|
className?: string;
|
|
12
|
-
} &
|
|
32
|
+
} & ComboStyleProp & GridPointerHandlers>;
|
|
13
33
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
export { HandMatrix, cellFillGradient, CELL_FILL_DIRECTION, } from './hand-matrix';
|
|
1
|
+
export { HandMatrix, cellFillGradient, CELL_FILL_DIRECTION, type CellPointerHandler, } from './hand-matrix';
|
|
2
2
|
export { RangeGrid } from './range-grid';
|
|
3
|
-
export { RangeMatrixEditor } from './editor/matrix-editor';
|
|
4
|
-
export type
|
|
5
|
-
export {
|
|
6
|
-
export
|
|
7
|
-
export {
|
|
8
|
-
export type { PaintTool } from './editor/use-range-painting';
|
|
9
|
-
export { seedCells, cellsToComboWeights, comboWeightsToCells, } from './editor/cells';
|
|
10
|
-
export type { EditorCell } from './editor/cells';
|
|
11
|
-
export { rebalance } from './editor/weights';
|
|
12
|
-
export type { EditorActionSpec } from './editor/weights';
|
|
3
|
+
export { RangeMatrixEditor, type RangeMatrixEditorProps, } from './editor/matrix-editor';
|
|
4
|
+
export { ActionWeightSliders, type ActionWeightSlidersProps, type WeightInputProps, } from './editor/action-sliders';
|
|
5
|
+
export { useRangePainting, type PaintTool } from './editor/use-range-painting';
|
|
6
|
+
export { seedCells, cellsToComboWeights, comboWeightsToCells, type EditorCell, } from './editor/cells';
|
|
7
|
+
export { rebalance, type EditorActionSpec } from './editor/weights';
|
package/dist/index.js
CHANGED
|
@@ -1,307 +1,334 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
var
|
|
1
|
+
import { useCallback as g, useEffect as y, useLayoutEffect as I, useMemo as x, useRef as v } from "react";
|
|
2
|
+
import { COMBOS as b, parseRange as S, toComboWeights as A } from "@spotlab/range";
|
|
3
|
+
import { jsx as h, jsxs as w } from "react/jsx-runtime";
|
|
4
|
+
var P = (t, e) => Array.from({ length: Math.ceil(t.length / e) }, (n, r) => t.slice(r * e, r * e + e)), D = "to right", N = (t) => t.length === 0 ? "transparent" : `linear-gradient(${D}, ${t.join(", ")})`, W = (t) => {
|
|
5
|
+
const e = t.target;
|
|
6
|
+
e.hasPointerCapture(t.pointerId) && e.releasePointerCapture(t.pointerId);
|
|
7
|
+
}, M = (t, e, n) => {
|
|
8
|
+
const r = t.ownerDocument.elementFromPoint(e, n)?.closest("[data-combo]");
|
|
9
|
+
return !r || !t.contains(r) ? null : r.getAttribute("data-combo");
|
|
10
|
+
}, T = ({ combo: t, onPointerDown: e, comboStyle: n }) => /* @__PURE__ */ h("div", {
|
|
5
11
|
className: "spotlab-hm-cell",
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
"data-combo": t,
|
|
13
|
+
style: n?.(t),
|
|
14
|
+
onPointerDown: e && ((r) => {
|
|
15
|
+
W(r), e(t, r);
|
|
16
|
+
}),
|
|
17
|
+
children: /* @__PURE__ */ h("div", {
|
|
11
18
|
className: "spotlab-hm-cell-inner",
|
|
12
|
-
children: /* @__PURE__ */
|
|
13
|
-
"data-combo":
|
|
14
|
-
children:
|
|
19
|
+
children: /* @__PURE__ */ h("div", {
|
|
20
|
+
"data-combo": t,
|
|
21
|
+
children: t
|
|
15
22
|
})
|
|
16
23
|
})
|
|
17
|
-
}),
|
|
24
|
+
}), B = ({ row: t, ...e }) => /* @__PURE__ */ h("div", {
|
|
18
25
|
className: "spotlab-hm-row",
|
|
19
|
-
children:
|
|
26
|
+
children: t.map((n, r) => /* @__PURE__ */ h(T, {
|
|
20
27
|
combo: n,
|
|
21
|
-
...
|
|
28
|
+
...e
|
|
22
29
|
}, r))
|
|
23
|
-
}),
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
}), F = {
|
|
31
|
+
touchAction: "none",
|
|
32
|
+
userSelect: "none",
|
|
33
|
+
WebkitUserSelect: "none",
|
|
34
|
+
WebkitTouchCallout: "none"
|
|
35
|
+
}, R = ({ className: t, onPointerDown: e, onPointerEnter: n, ...r }) => {
|
|
36
|
+
const a = v(null), o = (i) => {
|
|
37
|
+
const l = M(i.currentTarget, i.clientX, i.clientY);
|
|
38
|
+
l !== a.current && (a.current = l, l && n?.(l, i));
|
|
39
|
+
};
|
|
40
|
+
return /* @__PURE__ */ h("div", {
|
|
41
|
+
className: ["spotlab-hm-root", t].filter(Boolean).join(" "),
|
|
42
|
+
style: e ? F : void 0,
|
|
43
|
+
onPointerDown: n && ((i) => {
|
|
44
|
+
a.current = M(i.currentTarget, i.clientX, i.clientY);
|
|
45
|
+
}),
|
|
46
|
+
onPointerMove: n && o,
|
|
47
|
+
onPointerLeave: n && (() => a.current = null),
|
|
48
|
+
children: P(b, 13).map((i, l) => /* @__PURE__ */ h(B, {
|
|
49
|
+
row: i,
|
|
50
|
+
onPointerDown: e,
|
|
51
|
+
...r
|
|
52
|
+
}, l))
|
|
53
|
+
});
|
|
54
|
+
}, O = (t, e) => ({ background: N([`${t} 0 ${e * 100}%`, `transparent ${e * 100}% 100%`]) }), V = ({ range: t, fill: e = "var(--spotlab-hm-fill)", className: n }) => {
|
|
55
|
+
const r = x(() => {
|
|
56
|
+
const a = typeof t == "string" ? S(t) : t;
|
|
57
|
+
return A(a);
|
|
58
|
+
}, [t]);
|
|
59
|
+
return /* @__PURE__ */ h(R, {
|
|
35
60
|
className: n,
|
|
36
61
|
comboStyle: (a) => {
|
|
37
62
|
const o = r.get(a);
|
|
38
|
-
return o ?
|
|
63
|
+
return o ? O(e, o) : {};
|
|
39
64
|
}
|
|
40
65
|
});
|
|
41
|
-
},
|
|
42
|
-
const n =
|
|
66
|
+
}, X = (t) => Math.max(0, Math.min(100, Math.round(t))), j = (t, e) => {
|
|
67
|
+
const n = t.map(Math.round);
|
|
43
68
|
let r = 100 - n.reduce((a, o) => a + o, 0);
|
|
44
69
|
if (r !== 0) {
|
|
45
|
-
for (let a = 0; a < n.length; a++) if (a !==
|
|
70
|
+
for (let a = 0; a < n.length; a++) if (a !== e && n[a] + r >= 0) {
|
|
46
71
|
n[a] += r, r = 0;
|
|
47
72
|
break;
|
|
48
73
|
}
|
|
49
74
|
if (r !== 0) {
|
|
50
|
-
for (let a = 0; a < n.length; a++) if (a !==
|
|
75
|
+
for (let a = 0; a < n.length; a++) if (a !== e && n[a] > 0) {
|
|
51
76
|
const o = Math.min(n[a], -r);
|
|
52
77
|
if (n[a] -= o, r += o, r === 0) break;
|
|
53
78
|
}
|
|
54
|
-
n[
|
|
79
|
+
n[e] = Math.max(0, n[e] + r);
|
|
55
80
|
}
|
|
56
81
|
}
|
|
57
82
|
return n;
|
|
58
|
-
},
|
|
59
|
-
const a =
|
|
60
|
-
o[
|
|
83
|
+
}, _ = (t, e, n, r) => {
|
|
84
|
+
const a = X(n), o = t.slice();
|
|
85
|
+
o[e] = a;
|
|
61
86
|
const i = 100 - a, l = [];
|
|
62
|
-
for (let s = 0; s <
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
l.forEach((
|
|
66
|
-
const s =
|
|
87
|
+
for (let s = 0; s < t.length; s++) s !== e && l.push(s);
|
|
88
|
+
const u = l.reduce((s, p) => s + t[p], 0);
|
|
89
|
+
if (u <= 0) {
|
|
90
|
+
l.forEach((p) => o[p] = 0);
|
|
91
|
+
const s = e === r ? l[0] : r;
|
|
67
92
|
s != null && (o[s] = i);
|
|
68
|
-
} else l.forEach((s) => o[s] = Math.round(
|
|
69
|
-
return
|
|
70
|
-
},
|
|
71
|
-
const n =
|
|
72
|
-
let r =
|
|
93
|
+
} else l.forEach((s) => o[s] = Math.round(t[s] / u * i));
|
|
94
|
+
return j(o, e);
|
|
95
|
+
}, $ = (t, e) => t.reduce((n, r, a) => a === e ? n : n + r, 0), k = new Map(b.map((t, e) => [t, e])), L = (t, e) => {
|
|
96
|
+
const n = t.slice();
|
|
97
|
+
let r = $(n, e);
|
|
73
98
|
if (r > 100) {
|
|
74
99
|
const a = 100 / r;
|
|
75
|
-
for (let o = 0; o < n.length; o++) o !==
|
|
76
|
-
r =
|
|
100
|
+
for (let o = 0; o < n.length; o++) o !== e && (n[o] = Math.round(n[o] * a));
|
|
101
|
+
r = $(n, e);
|
|
77
102
|
}
|
|
78
|
-
return n[
|
|
103
|
+
return n[e] = Math.max(0, 100 - r), n;
|
|
79
104
|
};
|
|
80
|
-
function
|
|
81
|
-
return
|
|
82
|
-
const a =
|
|
105
|
+
function Z(t, e, n) {
|
|
106
|
+
return b.map((r) => {
|
|
107
|
+
const a = t ? t.get(r) ?? 0 : 1, o = e.map(() => 0);
|
|
83
108
|
return a <= 0 ? (o[n] = 100, {
|
|
84
109
|
avail: 0,
|
|
85
110
|
weights: o
|
|
86
|
-
}) : (
|
|
87
|
-
const
|
|
88
|
-
o[l] = Math.round(
|
|
111
|
+
}) : (e.forEach((i, l) => {
|
|
112
|
+
const u = i?.get(r) ?? 0;
|
|
113
|
+
o[l] = Math.round(u / a * 100);
|
|
89
114
|
}), {
|
|
90
115
|
avail: a,
|
|
91
|
-
weights:
|
|
116
|
+
weights: L(o, n)
|
|
92
117
|
});
|
|
93
118
|
});
|
|
94
119
|
}
|
|
95
|
-
var
|
|
96
|
-
function
|
|
120
|
+
var q = 8;
|
|
121
|
+
function z(t, e) {
|
|
97
122
|
const n = /* @__PURE__ */ new Map();
|
|
98
|
-
return
|
|
123
|
+
return t.forEach((r, a) => {
|
|
99
124
|
if (r.avail <= 0) return;
|
|
100
|
-
const o = r.weights.reduce((l,
|
|
125
|
+
const o = r.weights.reduce((l, u) => l + u, 0);
|
|
101
126
|
if (!o) return;
|
|
102
|
-
const i = Number((r.weights[
|
|
103
|
-
i && n.set(
|
|
127
|
+
const i = Number((r.weights[e] / o * r.avail).toFixed(q));
|
|
128
|
+
i && n.set(b[a], i);
|
|
104
129
|
}), n;
|
|
105
130
|
}
|
|
106
|
-
function
|
|
107
|
-
return
|
|
108
|
-
const o =
|
|
131
|
+
function tt(t, e, n) {
|
|
132
|
+
return t.map((r, a) => {
|
|
133
|
+
const o = e.map(() => 0);
|
|
109
134
|
if (r.avail <= 0)
|
|
110
135
|
return o[n] = 100, {
|
|
111
136
|
avail: r.avail,
|
|
112
137
|
weights: o
|
|
113
138
|
};
|
|
114
|
-
const i =
|
|
115
|
-
return
|
|
139
|
+
const i = b[a];
|
|
140
|
+
return e.forEach((l, u) => {
|
|
116
141
|
const s = l?.get(i) ?? 0;
|
|
117
|
-
o[
|
|
142
|
+
o[u] = Math.round(s / r.avail * 100);
|
|
118
143
|
}), {
|
|
119
144
|
avail: r.avail,
|
|
120
|
-
weights:
|
|
145
|
+
weights: L(o, n)
|
|
121
146
|
};
|
|
122
147
|
});
|
|
123
148
|
}
|
|
124
|
-
function
|
|
125
|
-
const { cells:
|
|
126
|
-
|
|
127
|
-
const u = () => l.current = !1;
|
|
128
|
-
return window.addEventListener("pointerup", u), () => window.removeEventListener("pointerup", u);
|
|
149
|
+
function G(t) {
|
|
150
|
+
const { cells: e, brush: n, tool: r, onCellsChange: a, onBrushChange: o, onToolChange: i } = t, l = v(null), u = g(() => {
|
|
151
|
+
l.current?.abort(), l.current = null;
|
|
129
152
|
}, []);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
y(() => u, [u]);
|
|
154
|
+
const s = v(e);
|
|
155
|
+
I(() => {
|
|
156
|
+
s.current = e;
|
|
157
|
+
}, [e]);
|
|
158
|
+
const p = g((c) => {
|
|
159
|
+
const d = s.current, f = k.get(c);
|
|
160
|
+
if (d[f].avail <= 0) return;
|
|
161
|
+
const C = d.slice();
|
|
162
|
+
C[f] = {
|
|
163
|
+
avail: d[f].avail,
|
|
136
164
|
weights: [...n]
|
|
137
|
-
}, a(
|
|
138
|
-
}, [
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
a
|
|
142
|
-
]), s = v((u) => {
|
|
143
|
-
const d = C.get(u);
|
|
144
|
-
t[d].avail <= 0 || (o([...t[d].weights]), i("brush"));
|
|
165
|
+
}, s.current = C, a(C);
|
|
166
|
+
}, [n, a]), m = g((c) => {
|
|
167
|
+
const d = k.get(c);
|
|
168
|
+
e[d].avail <= 0 || (o([...e[d].weights]), i("brush"));
|
|
145
169
|
}, [
|
|
146
|
-
|
|
170
|
+
e,
|
|
147
171
|
o,
|
|
148
172
|
i
|
|
149
173
|
]);
|
|
150
174
|
return {
|
|
151
|
-
onCellDown:
|
|
175
|
+
onCellDown: g((c) => {
|
|
152
176
|
if (r === "eyedropper") {
|
|
153
|
-
|
|
177
|
+
m(c);
|
|
154
178
|
return;
|
|
155
179
|
}
|
|
156
|
-
|
|
180
|
+
u();
|
|
181
|
+
const d = new AbortController();
|
|
182
|
+
l.current = d, window.addEventListener("pointerup", u, { signal: d.signal }), window.addEventListener("pointercancel", u, { signal: d.signal }), p(c);
|
|
157
183
|
}, [
|
|
158
184
|
r,
|
|
159
|
-
|
|
160
|
-
|
|
185
|
+
m,
|
|
186
|
+
p,
|
|
187
|
+
u
|
|
161
188
|
]),
|
|
162
|
-
onCellEnter:
|
|
163
|
-
l.current && r === "brush" && c
|
|
164
|
-
}, [r,
|
|
189
|
+
onCellEnter: g((c) => {
|
|
190
|
+
l.current && r === "brush" && p(c);
|
|
191
|
+
}, [r, p])
|
|
165
192
|
};
|
|
166
193
|
}
|
|
167
|
-
var E = (
|
|
194
|
+
var E = (t, e, n) => {
|
|
168
195
|
const r = [];
|
|
169
196
|
let a = 0;
|
|
170
197
|
return n.forEach((o) => {
|
|
171
|
-
const i = (
|
|
198
|
+
const i = (t.weights[o] || 0) / 100;
|
|
172
199
|
if (i <= 0) return;
|
|
173
|
-
const l =
|
|
200
|
+
const l = e[o].color;
|
|
174
201
|
r.push(`${l} ${a * 100}%`), r.push(`${l} ${(a + i) * 100}%`), a += i;
|
|
175
|
-
}), $(r);
|
|
176
|
-
},
|
|
177
|
-
const { onCellDown: s, onCellEnter:
|
|
178
|
-
cells:
|
|
202
|
+
}), r.length > 0 && a < 1 - 1e-9 && r.push(`transparent ${a * 100}% 100%`), N(r);
|
|
203
|
+
}, et = ({ cells: t, actions: e, order: n, brush: r, tool: a, onCellsChange: o, onBrushChange: i, onToolChange: l, className: u }) => {
|
|
204
|
+
const { onCellDown: s, onCellEnter: p } = G({
|
|
205
|
+
cells: t,
|
|
179
206
|
brush: r,
|
|
180
207
|
tool: a,
|
|
181
208
|
onCellsChange: o,
|
|
182
209
|
onBrushChange: i,
|
|
183
210
|
onToolChange: l
|
|
184
211
|
});
|
|
185
|
-
return /* @__PURE__ */
|
|
186
|
-
className:
|
|
187
|
-
comboStyle: (
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
212
|
+
return /* @__PURE__ */ h(R, {
|
|
213
|
+
className: u,
|
|
214
|
+
comboStyle: (m) => {
|
|
215
|
+
const c = t[k.get(m)];
|
|
216
|
+
if (c.avail <= 0) return {
|
|
190
217
|
background: "var(--spotlab-hm-dead-hatch)",
|
|
191
218
|
backgroundColor: "var(--spotlab-hm-muted-bg)",
|
|
192
219
|
cursor: "not-allowed",
|
|
193
220
|
color: "var(--spotlab-hm-muted-fg)"
|
|
194
221
|
};
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
const f = `${
|
|
222
|
+
const d = a === "eyedropper" ? "copy" : "pointer";
|
|
223
|
+
if (c.avail < 1) {
|
|
224
|
+
const f = `${c.avail * 100}%`;
|
|
198
225
|
return {
|
|
199
|
-
background: `repeating-linear-gradient(180deg, var(--spotlab-hm-muted-fg) 0 4px, transparent 4px 8px), var(--spotlab-hm-dead-hatch), ${E(
|
|
200
|
-
backgroundSize: `1px 100%, ${(1 -
|
|
226
|
+
background: `repeating-linear-gradient(180deg, var(--spotlab-hm-muted-fg) 0 4px, transparent 4px 8px), var(--spotlab-hm-dead-hatch), ${E(c, e, n)}`,
|
|
227
|
+
backgroundSize: `1px 100%, ${(1 - c.avail) * 100}% 100%, ${c.avail * 100}% 100%`,
|
|
201
228
|
backgroundPosition: `${f} center, right center, left center`,
|
|
202
229
|
backgroundRepeat: "no-repeat, no-repeat, no-repeat",
|
|
203
|
-
cursor:
|
|
230
|
+
cursor: d
|
|
204
231
|
};
|
|
205
232
|
}
|
|
206
233
|
return {
|
|
207
|
-
background: E(
|
|
208
|
-
cursor:
|
|
234
|
+
background: E(c, e, n),
|
|
235
|
+
cursor: d
|
|
209
236
|
};
|
|
210
237
|
},
|
|
211
|
-
|
|
212
|
-
|
|
238
|
+
onPointerDown: s,
|
|
239
|
+
onPointerEnter: p
|
|
213
240
|
});
|
|
214
|
-
},
|
|
241
|
+
}, U = (t) => /* @__PURE__ */ h("input", {
|
|
215
242
|
type: "number",
|
|
216
243
|
className: "spotlab-hm-weight-input",
|
|
217
|
-
...
|
|
218
|
-
}),
|
|
219
|
-
const a =
|
|
220
|
-
const
|
|
221
|
-
if (!
|
|
222
|
-
const s =
|
|
244
|
+
...t
|
|
245
|
+
}), Y = ({ value: t, color: e, passive: n, onChange: r }) => {
|
|
246
|
+
const a = v(null), o = v(!1), i = g((l) => {
|
|
247
|
+
const u = a.current;
|
|
248
|
+
if (!u) return;
|
|
249
|
+
const s = u.getBoundingClientRect();
|
|
223
250
|
r((l - s.left) / s.width * 100);
|
|
224
251
|
}, [r]);
|
|
225
|
-
return
|
|
252
|
+
return y(() => {
|
|
226
253
|
const l = (s) => {
|
|
227
254
|
o.current && i(s.clientX);
|
|
228
|
-
},
|
|
229
|
-
return window.addEventListener("pointermove", l), window.addEventListener("pointerup",
|
|
230
|
-
window.removeEventListener("pointermove", l), window.removeEventListener("pointerup",
|
|
255
|
+
}, u = () => o.current = !1;
|
|
256
|
+
return window.addEventListener("pointermove", l), window.addEventListener("pointerup", u), () => {
|
|
257
|
+
window.removeEventListener("pointermove", l), window.removeEventListener("pointerup", u);
|
|
231
258
|
};
|
|
232
|
-
}, [i]), /* @__PURE__ */
|
|
259
|
+
}, [i]), /* @__PURE__ */ h("div", {
|
|
233
260
|
ref: a,
|
|
234
261
|
role: "slider",
|
|
235
|
-
"aria-valuenow":
|
|
262
|
+
"aria-valuenow": t,
|
|
236
263
|
"aria-valuemin": 0,
|
|
237
264
|
"aria-valuemax": 100,
|
|
238
265
|
className: "spotlab-hm-slider-track",
|
|
239
|
-
style: { background: `linear-gradient(to right, ${
|
|
266
|
+
style: { background: `linear-gradient(to right, ${e} 0 ${t}%, var(--spotlab-hm-track-bg) ${t}% 100%)` },
|
|
240
267
|
onPointerDown: (l) => {
|
|
241
268
|
o.current = !0, i(l.clientX);
|
|
242
269
|
},
|
|
243
|
-
children: /* @__PURE__ */
|
|
270
|
+
children: /* @__PURE__ */ h("div", {
|
|
244
271
|
className: "spotlab-hm-slider-thumb",
|
|
245
272
|
style: {
|
|
246
|
-
left: `${
|
|
247
|
-
border: `2px solid ${n ? "var(--spotlab-hm-muted-fg)" :
|
|
273
|
+
left: `${t}%`,
|
|
274
|
+
border: `2px solid ${n ? "var(--spotlab-hm-muted-fg)" : e}`
|
|
248
275
|
}
|
|
249
276
|
})
|
|
250
277
|
});
|
|
251
|
-
},
|
|
278
|
+
}, H = (t, e) => /* @__PURE__ */ w("span", {
|
|
252
279
|
className: "spotlab-hm-slider-label",
|
|
253
|
-
children: [
|
|
280
|
+
children: [t.label, e && /* @__PURE__ */ h("span", {
|
|
254
281
|
className: "spotlab-hm-slider-remainder",
|
|
255
282
|
children: " · remainder"
|
|
256
283
|
})]
|
|
257
|
-
}),
|
|
258
|
-
const s = l?.WeightInput ??
|
|
259
|
-
a(
|
|
284
|
+
}), rt = ({ actions: t, order: e, weights: n, passive: r, onWeightsChange: a, renderActionLabel: o, renderActionExtra: i, components: l, className: u }) => {
|
|
285
|
+
const s = l?.WeightInput ?? U, p = (m, c) => {
|
|
286
|
+
a(_(n, m, c, r));
|
|
260
287
|
};
|
|
261
|
-
return /* @__PURE__ */
|
|
262
|
-
className: ["spotlab-hm-root spotlab-hm-sliders",
|
|
263
|
-
children:
|
|
264
|
-
const
|
|
265
|
-
return /* @__PURE__ */
|
|
288
|
+
return /* @__PURE__ */ h("div", {
|
|
289
|
+
className: ["spotlab-hm-root spotlab-hm-sliders", u].filter(Boolean).join(" "),
|
|
290
|
+
children: e.map((m) => {
|
|
291
|
+
const c = t[m], d = m === r;
|
|
292
|
+
return /* @__PURE__ */ w("div", {
|
|
266
293
|
className: "spotlab-hm-slider-row",
|
|
267
|
-
"data-passive":
|
|
268
|
-
children: [/* @__PURE__ */
|
|
294
|
+
"data-passive": d,
|
|
295
|
+
children: [/* @__PURE__ */ w("div", {
|
|
269
296
|
className: "spotlab-hm-slider-header",
|
|
270
297
|
children: [
|
|
271
|
-
/* @__PURE__ */
|
|
298
|
+
/* @__PURE__ */ h("span", {
|
|
272
299
|
className: "spotlab-hm-slider-swatch",
|
|
273
|
-
style: { background:
|
|
300
|
+
style: { background: c.color }
|
|
274
301
|
}),
|
|
275
|
-
o?.(
|
|
276
|
-
i?.(
|
|
302
|
+
o?.(c, m, d) ?? H(c, d),
|
|
303
|
+
i?.(c, m)
|
|
277
304
|
]
|
|
278
|
-
}), /* @__PURE__ */
|
|
305
|
+
}), /* @__PURE__ */ w("div", {
|
|
279
306
|
className: "spotlab-hm-slider-track-row",
|
|
280
|
-
children: [/* @__PURE__ */
|
|
281
|
-
value: n[
|
|
282
|
-
color:
|
|
283
|
-
passive:
|
|
284
|
-
onChange: (f) =>
|
|
285
|
-
}), /* @__PURE__ */
|
|
286
|
-
value: n[
|
|
287
|
-
"aria-label": `${
|
|
288
|
-
onChange: (f) =>
|
|
307
|
+
children: [/* @__PURE__ */ h(Y, {
|
|
308
|
+
value: n[m],
|
|
309
|
+
color: c.color,
|
|
310
|
+
passive: d,
|
|
311
|
+
onChange: (f) => p(m, f)
|
|
312
|
+
}), /* @__PURE__ */ h(s, {
|
|
313
|
+
value: n[m],
|
|
314
|
+
"aria-label": `${c.label} weight`,
|
|
315
|
+
onChange: (f) => p(m, parseInt(f.target.value || "0", 10))
|
|
289
316
|
})]
|
|
290
317
|
})]
|
|
291
|
-
},
|
|
318
|
+
}, c.id);
|
|
292
319
|
})
|
|
293
320
|
});
|
|
294
321
|
};
|
|
295
322
|
export {
|
|
296
|
-
|
|
323
|
+
rt as ActionWeightSliders,
|
|
297
324
|
D as CELL_FILL_DIRECTION,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
325
|
+
R as HandMatrix,
|
|
326
|
+
V as RangeGrid,
|
|
327
|
+
et as RangeMatrixEditor,
|
|
328
|
+
N as cellFillGradient,
|
|
329
|
+
z as cellsToComboWeights,
|
|
330
|
+
tt as comboWeightsToCells,
|
|
331
|
+
_ as rebalance,
|
|
332
|
+
Z as seedCells,
|
|
333
|
+
G as useRangePainting
|
|
307
334
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spotlab/hand-matrix",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A 13x13 poker hand-matrix React component: the read-only grid primitive and a range-string-aware display component, themed through CSS custom properties.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"LICENSE"
|
|
41
41
|
],
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@spotlab/range": "^0.
|
|
43
|
+
"@spotlab/range": "^0.2.0",
|
|
44
44
|
"react": "^19.0.0",
|
|
45
45
|
"react-dom": "^19.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@spotlab/range": "0.
|
|
48
|
+
"@spotlab/range": "0.2.0",
|
|
49
49
|
"@storybook/addon-vitest": "^10.5.5",
|
|
50
50
|
"@storybook/react-vite": "^10.5.5",
|
|
51
51
|
"@types/react": "^19.2.16",
|
|
@@ -73,6 +73,6 @@
|
|
|
73
73
|
"test": "vitest run --project unit",
|
|
74
74
|
"storybook": "storybook dev -p 6007",
|
|
75
75
|
"build-storybook": "storybook build",
|
|
76
|
-
"test-storybook": "vitest run --project storybook"
|
|
76
|
+
"test-storybook": "NODE_ENV=production vitest run --project storybook"
|
|
77
77
|
}
|
|
78
78
|
}
|