@spotlab/hand-matrix 0.1.0 → 0.3.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 +95 -4
- package/dist/editor/cells.d.ts +21 -0
- 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 +219 -170
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -16,20 +16,111 @@ 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
|
+
- `cellsToRanges(original, seed, cells): HandFrequency[][]` — turn the
|
|
99
|
+
edited grid back into one hand-keyed range per action. `original` holds
|
|
100
|
+
each action's range (or `null`) in editor action order, `seed` the cells
|
|
101
|
+
the editor opened with, and `cells` the cells after editing. Combos whose
|
|
102
|
+
cell still matches its seed keep each action's original hands verbatim;
|
|
103
|
+
changed combos, and every combo of a `null` action, are rebuilt from the
|
|
104
|
+
cells.
|
|
105
|
+
- `comboWeightsToCells(cells, actionWeights, passive): EditorCell[]` — the
|
|
106
|
+
inverse: re-derive the cells' conditional weights from absolute
|
|
107
|
+
per-action ranges, keeping each cell's `avail`.
|
|
108
|
+
- `rebalance(weights, index, value, passive): number[]` — set one action's
|
|
109
|
+
weight and rescale the rest so the row sums to 100. This is what
|
|
110
|
+
`ActionWeightSliders` uses.
|
|
111
|
+
- Types: `EditorCell` (`{ avail: number; weights: number[] }`, where `avail`
|
|
112
|
+
is the combo's cap in the parent range and `weights` are each action's
|
|
113
|
+
share of it, summing to 100), `EditorActionSpec`
|
|
114
|
+
(`{ id: string; label: string; color: string }`), `PaintTool`,
|
|
115
|
+
`CellPointerHandler`, `RangeMatrixEditorProps`, `ActionWeightSlidersProps`,
|
|
116
|
+
`WeightInputProps`.
|
|
117
|
+
|
|
118
|
+
**The editor works at combo level.** A cell is one of the 169 combos (`AKs`,
|
|
119
|
+
`AKo`, `QQ`) at a whole-percent weight, so suit-specific hands such as
|
|
120
|
+
`AsKd:0.7` and fractional weights such as `QQ:0.333` can't be shown exactly:
|
|
121
|
+
`seedCells` takes combo weights, and `cellsToComboWeights` returns them. Save
|
|
122
|
+
with `cellsToRanges` to keep them for every combo the user didn't repaint.
|
|
123
|
+
|
|
33
124
|
## Theming
|
|
34
125
|
|
|
35
126
|
The package ships `hand-matrix.css` (`@spotlab/hand-matrix/style.css`),
|
package/dist/editor/cells.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HandFrequency } from '@spotlab/range';
|
|
1
2
|
export declare const COMBO_INDEX: Map<string, number>;
|
|
2
3
|
/**
|
|
3
4
|
* One matrix cell's painted state: `avail` is the combo's cap (its weight in the
|
|
@@ -8,6 +9,8 @@ export type EditorCell = {
|
|
|
8
9
|
avail: number;
|
|
9
10
|
weights: number[];
|
|
10
11
|
};
|
|
12
|
+
/** A copy of `cells` with `combo`'s cell replaced by `cell`. */
|
|
13
|
+
export declare const withCell: (cells: EditorCell[], combo: string, cell: EditorCell) => EditorCell[];
|
|
11
14
|
/**
|
|
12
15
|
* Seed the 169-cell grid from a parent range's per-combo availability and each
|
|
13
16
|
* action's own per-combo range, both expressed as absolute weights (a share of
|
|
@@ -24,3 +27,21 @@ export declare function cellsToComboWeights(cells: EditorCell[], actionIndex: nu
|
|
|
24
27
|
* `actionWeights` naming the remainder action.
|
|
25
28
|
*/
|
|
26
29
|
export declare function comboWeightsToCells(cells: EditorCell[], actionWeights: (Map<string, number> | null)[], passive: number): EditorCell[];
|
|
30
|
+
/**
|
|
31
|
+
* Turn an edited grid back into one hand-keyed range per action, keeping each
|
|
32
|
+
* action's original hands wherever the editor didn't touch them. `original`
|
|
33
|
+
* holds each action's range in editor action order, `seed` the cells the
|
|
34
|
+
* editor opened with, and `cells` the cells after editing.
|
|
35
|
+
*
|
|
36
|
+
* A combo whose cell still equals its seed cell (same `avail`, same weights)
|
|
37
|
+
* keeps every action's original hands verbatim, so input the whole-percent grid
|
|
38
|
+
* can't represent — a single suit (`AhKh:0.7`) or a fractional weight
|
|
39
|
+
* (`QQ:0.333`) — survives a save. A changed combo is rebuilt from the cells as
|
|
40
|
+
* `cellsToComboWeights` does. An action whose original is `null` (e.g. an
|
|
41
|
+
* implicit fold) is always rebuilt from the cells in full.
|
|
42
|
+
*
|
|
43
|
+
* Each rebuilt combo takes its first original hand's place, and combos the
|
|
44
|
+
* original didn't have follow in matrix order, so an unedited range formats
|
|
45
|
+
* back to the string it came from.
|
|
46
|
+
*/
|
|
47
|
+
export declare function cellsToRanges(original: (HandFrequency[] | null)[], seed: EditorCell[], cells: EditorCell[]): HandFrequency[][];
|
|
@@ -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, cellsToRanges, comboWeightsToCells, type EditorCell, } from './editor/cells';
|
|
7
|
+
export { rebalance, type EditorActionSpec } from './editor/weights';
|
package/dist/index.js
CHANGED
|
@@ -1,307 +1,356 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
var
|
|
1
|
+
import { useCallback as g, useEffect as N, useLayoutEffect as S, useMemo as I, useRef as b } from "react";
|
|
2
|
+
import { COMBOS as v, comboOf as A, expandComboWeights as M, parseRange as P, toComboWeights as W } from "@spotlab/range";
|
|
3
|
+
import { jsx as p, jsxs as w } from "react/jsx-runtime";
|
|
4
|
+
var D = (t, e) => Array.from({ length: Math.ceil(t.length / e) }, (n, r) => t.slice(r * e, r * e + e)), T = "to right", R = (t) => t.length === 0 ? "transparent" : `linear-gradient(${T}, ${t.join(", ")})`, O = (t) => {
|
|
5
|
+
const e = t.target;
|
|
6
|
+
e.hasPointerCapture(t.pointerId) && e.releasePointerCapture(t.pointerId);
|
|
7
|
+
}, $ = (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
|
+
}, B = ({ combo: t, onPointerDown: e, comboStyle: n }) => /* @__PURE__ */ p("div", {
|
|
5
11
|
className: "spotlab-hm-cell",
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
"data-combo": t,
|
|
13
|
+
style: n?.(t),
|
|
14
|
+
onPointerDown: e && ((r) => {
|
|
15
|
+
O(r), e(t, r);
|
|
16
|
+
}),
|
|
10
17
|
children: /* @__PURE__ */ p("div", {
|
|
11
18
|
className: "spotlab-hm-cell-inner",
|
|
12
19
|
children: /* @__PURE__ */ p("div", {
|
|
13
|
-
"data-combo":
|
|
14
|
-
children:
|
|
20
|
+
"data-combo": t,
|
|
21
|
+
children: t
|
|
15
22
|
})
|
|
16
23
|
})
|
|
17
|
-
}),
|
|
24
|
+
}), F = ({ row: t, ...e }) => /* @__PURE__ */ p("div", {
|
|
18
25
|
className: "spotlab-hm-row",
|
|
19
|
-
children:
|
|
26
|
+
children: t.map((n, r) => /* @__PURE__ */ p(B, {
|
|
20
27
|
combo: n,
|
|
21
|
-
...
|
|
28
|
+
...e
|
|
22
29
|
}, r))
|
|
23
|
-
}),
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
}), X = {
|
|
31
|
+
touchAction: "none",
|
|
32
|
+
userSelect: "none",
|
|
33
|
+
WebkitUserSelect: "none",
|
|
34
|
+
WebkitTouchCallout: "none"
|
|
35
|
+
}, x = ({ className: t, onPointerDown: e, onPointerEnter: n, ...r }) => {
|
|
36
|
+
const a = b(null), o = (s) => {
|
|
37
|
+
const l = $(s.currentTarget, s.clientX, s.clientY);
|
|
38
|
+
l !== a.current && (a.current = l, l && n?.(l, s));
|
|
39
|
+
};
|
|
40
|
+
return /* @__PURE__ */ p("div", {
|
|
41
|
+
className: ["spotlab-hm-root", t].filter(Boolean).join(" "),
|
|
42
|
+
style: e ? X : void 0,
|
|
43
|
+
onPointerDown: n && ((s) => {
|
|
44
|
+
a.current = $(s.currentTarget, s.clientX, s.clientY);
|
|
45
|
+
}),
|
|
46
|
+
onPointerMove: n && o,
|
|
47
|
+
onPointerLeave: n && (() => a.current = null),
|
|
48
|
+
children: D(v, 13).map((s, l) => /* @__PURE__ */ p(F, {
|
|
49
|
+
row: s,
|
|
50
|
+
onPointerDown: e,
|
|
51
|
+
...r
|
|
52
|
+
}, l))
|
|
53
|
+
});
|
|
54
|
+
}, _ = (t, e) => ({ background: R([`${t} 0 ${e * 100}%`, `transparent ${e * 100}% 100%`]) }), rt = ({ range: t, fill: e = "var(--spotlab-hm-fill)", className: n }) => {
|
|
55
|
+
const r = I(() => {
|
|
56
|
+
const a = typeof t == "string" ? P(t) : t;
|
|
57
|
+
return W(a);
|
|
58
|
+
}, [t]);
|
|
59
|
+
return /* @__PURE__ */ p(x, {
|
|
35
60
|
className: n,
|
|
36
61
|
comboStyle: (a) => {
|
|
37
62
|
const o = r.get(a);
|
|
38
|
-
return o ?
|
|
63
|
+
return o ? _(e, o) : {};
|
|
39
64
|
}
|
|
40
65
|
});
|
|
41
|
-
},
|
|
42
|
-
const n =
|
|
66
|
+
}, j = (t) => Math.max(0, Math.min(100, Math.round(t))), q = (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[
|
|
61
|
-
const
|
|
62
|
-
for (let
|
|
63
|
-
const c = l.reduce((
|
|
83
|
+
}, G = (t, e, n, r) => {
|
|
84
|
+
const a = j(n), o = t.slice();
|
|
85
|
+
o[e] = a;
|
|
86
|
+
const s = 100 - a, l = [];
|
|
87
|
+
for (let i = 0; i < t.length; i++) i !== e && l.push(i);
|
|
88
|
+
const c = l.reduce((i, d) => i + t[d], 0);
|
|
64
89
|
if (c <= 0) {
|
|
65
|
-
l.forEach((
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
} else l.forEach((
|
|
69
|
-
return
|
|
70
|
-
},
|
|
71
|
-
const
|
|
72
|
-
|
|
90
|
+
l.forEach((d) => o[d] = 0);
|
|
91
|
+
const i = e === r ? l[0] : r;
|
|
92
|
+
i != null && (o[i] = s);
|
|
93
|
+
} else l.forEach((i) => o[i] = Math.round(t[i] / c * s));
|
|
94
|
+
return q(o, e);
|
|
95
|
+
}, E = (t, e) => t.reduce((n, r, a) => a === e ? n : n + r, 0), C = new Map(v.map((t, e) => [t, e])), U = (t, e, n) => {
|
|
96
|
+
const r = t.slice();
|
|
97
|
+
return r[C.get(e)] = n, r;
|
|
98
|
+
}, L = (t, e) => {
|
|
99
|
+
const n = t.slice();
|
|
100
|
+
let r = E(n, e);
|
|
73
101
|
if (r > 100) {
|
|
74
102
|
const a = 100 / r;
|
|
75
|
-
for (let o = 0; o < n.length; o++) o !==
|
|
76
|
-
r =
|
|
103
|
+
for (let o = 0; o < n.length; o++) o !== e && (n[o] = Math.round(n[o] * a));
|
|
104
|
+
r = E(n, e);
|
|
77
105
|
}
|
|
78
|
-
return n[
|
|
106
|
+
return n[e] = Math.max(0, 100 - r), n;
|
|
79
107
|
};
|
|
80
|
-
function
|
|
81
|
-
return
|
|
82
|
-
const a =
|
|
108
|
+
function nt(t, e, n) {
|
|
109
|
+
return v.map((r) => {
|
|
110
|
+
const a = t ? t.get(r) ?? 0 : 1, o = e.map(() => 0);
|
|
83
111
|
return a <= 0 ? (o[n] = 100, {
|
|
84
112
|
avail: 0,
|
|
85
113
|
weights: o
|
|
86
|
-
}) : (
|
|
87
|
-
const c =
|
|
114
|
+
}) : (e.forEach((s, l) => {
|
|
115
|
+
const c = s?.get(r) ?? 0;
|
|
88
116
|
o[l] = Math.round(c / a * 100);
|
|
89
117
|
}), {
|
|
90
118
|
avail: a,
|
|
91
|
-
weights:
|
|
119
|
+
weights: L(o, n)
|
|
92
120
|
});
|
|
93
121
|
});
|
|
94
122
|
}
|
|
95
|
-
var
|
|
96
|
-
function
|
|
123
|
+
var Y = 8;
|
|
124
|
+
function H(t, e) {
|
|
97
125
|
const n = /* @__PURE__ */ new Map();
|
|
98
|
-
return
|
|
126
|
+
return t.forEach((r, a) => {
|
|
99
127
|
if (r.avail <= 0) return;
|
|
100
128
|
const o = r.weights.reduce((l, c) => l + c, 0);
|
|
101
129
|
if (!o) return;
|
|
102
|
-
const
|
|
103
|
-
|
|
130
|
+
const s = Number((r.weights[e] / o * r.avail).toFixed(Y));
|
|
131
|
+
s && n.set(v[a], s);
|
|
104
132
|
}), n;
|
|
105
133
|
}
|
|
106
|
-
function
|
|
107
|
-
return
|
|
108
|
-
const o =
|
|
134
|
+
function at(t, e, n) {
|
|
135
|
+
return t.map((r, a) => {
|
|
136
|
+
const o = e.map(() => 0);
|
|
109
137
|
if (r.avail <= 0)
|
|
110
138
|
return o[n] = 100, {
|
|
111
139
|
avail: r.avail,
|
|
112
140
|
weights: o
|
|
113
141
|
};
|
|
114
|
-
const
|
|
115
|
-
return
|
|
116
|
-
const
|
|
117
|
-
o[c] = Math.round(
|
|
142
|
+
const s = v[a];
|
|
143
|
+
return e.forEach((l, c) => {
|
|
144
|
+
const i = l?.get(s) ?? 0;
|
|
145
|
+
o[c] = Math.round(i / r.avail * 100);
|
|
118
146
|
}), {
|
|
119
147
|
avail: r.avail,
|
|
120
|
-
weights:
|
|
148
|
+
weights: L(o, n)
|
|
121
149
|
};
|
|
122
150
|
});
|
|
123
151
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
152
|
+
var Q = (t, e) => t.avail !== e.avail || t.weights.length !== e.weights.length || t.weights.some((n, r) => n !== e.weights[r]);
|
|
153
|
+
function ot(t, e, n) {
|
|
154
|
+
const r = new Set(v.filter((a, o) => Q(e[o], n[o])));
|
|
155
|
+
return t.map((a, o) => {
|
|
156
|
+
const s = H(n, o);
|
|
157
|
+
if (a === null) return M(s);
|
|
158
|
+
const l = (d) => {
|
|
159
|
+
const h = s.get(d);
|
|
160
|
+
return h ? M(/* @__PURE__ */ new Map([[d, h]])) : [];
|
|
161
|
+
}, c = [], i = /* @__PURE__ */ new Set();
|
|
162
|
+
for (const d of a) {
|
|
163
|
+
const h = A(d.hand);
|
|
164
|
+
r.has(h) ? i.has(h) || (i.add(h), c.push(...l(h))) : c.push(d);
|
|
165
|
+
}
|
|
166
|
+
for (const d of r) i.has(d) || c.push(...l(d));
|
|
167
|
+
return c;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function J(t) {
|
|
171
|
+
const { cells: e, brush: n, tool: r, onCellsChange: a, onBrushChange: o, onToolChange: s } = t, l = b(null), c = g(() => {
|
|
172
|
+
l.current?.abort(), l.current = null;
|
|
129
173
|
}, []);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
174
|
+
N(() => c, [c]);
|
|
175
|
+
const i = b(e);
|
|
176
|
+
S(() => {
|
|
177
|
+
i.current = e;
|
|
178
|
+
}, [e]);
|
|
179
|
+
const d = g((u) => {
|
|
180
|
+
const f = i.current, { avail: m } = f[C.get(u)];
|
|
181
|
+
if (m <= 0) return;
|
|
182
|
+
const k = U(f, u, {
|
|
183
|
+
avail: m,
|
|
136
184
|
weights: [...n]
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
]), s = v((u) => {
|
|
143
|
-
const d = C.get(u);
|
|
144
|
-
t[d].avail <= 0 || (o([...t[d].weights]), i("brush"));
|
|
185
|
+
});
|
|
186
|
+
i.current = k, a(k);
|
|
187
|
+
}, [n, a]), h = g((u) => {
|
|
188
|
+
const f = C.get(u);
|
|
189
|
+
e[f].avail <= 0 || (o([...e[f].weights]), s("brush"));
|
|
145
190
|
}, [
|
|
146
|
-
|
|
191
|
+
e,
|
|
147
192
|
o,
|
|
148
|
-
|
|
193
|
+
s
|
|
149
194
|
]);
|
|
150
195
|
return {
|
|
151
|
-
onCellDown:
|
|
196
|
+
onCellDown: g((u) => {
|
|
152
197
|
if (r === "eyedropper") {
|
|
153
|
-
|
|
198
|
+
h(u);
|
|
154
199
|
return;
|
|
155
200
|
}
|
|
156
|
-
|
|
201
|
+
c();
|
|
202
|
+
const f = new AbortController();
|
|
203
|
+
l.current = f, window.addEventListener("pointerup", c, { signal: f.signal }), window.addEventListener("pointercancel", c, { signal: f.signal }), d(u);
|
|
157
204
|
}, [
|
|
158
205
|
r,
|
|
159
|
-
|
|
206
|
+
h,
|
|
207
|
+
d,
|
|
160
208
|
c
|
|
161
209
|
]),
|
|
162
|
-
onCellEnter:
|
|
163
|
-
l.current && r === "brush" &&
|
|
164
|
-
}, [r,
|
|
210
|
+
onCellEnter: g((u) => {
|
|
211
|
+
l.current && r === "brush" && d(u);
|
|
212
|
+
}, [r, d])
|
|
165
213
|
};
|
|
166
214
|
}
|
|
167
|
-
var
|
|
215
|
+
var y = (t, e, n) => {
|
|
168
216
|
const r = [];
|
|
169
217
|
let a = 0;
|
|
170
218
|
return n.forEach((o) => {
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
-
const l =
|
|
174
|
-
r.push(`${l} ${a * 100}%`), r.push(`${l} ${(a +
|
|
175
|
-
}), $(r);
|
|
176
|
-
},
|
|
177
|
-
const { onCellDown:
|
|
178
|
-
cells:
|
|
219
|
+
const s = (t.weights[o] || 0) / 100;
|
|
220
|
+
if (s <= 0) return;
|
|
221
|
+
const l = e[o].color;
|
|
222
|
+
r.push(`${l} ${a * 100}%`), r.push(`${l} ${(a + s) * 100}%`), a += s;
|
|
223
|
+
}), r.length > 0 && a < 1 - 1e-9 && r.push(`transparent ${a * 100}% 100%`), R(r);
|
|
224
|
+
}, lt = ({ cells: t, actions: e, order: n, brush: r, tool: a, onCellsChange: o, onBrushChange: s, onToolChange: l, className: c }) => {
|
|
225
|
+
const { onCellDown: i, onCellEnter: d } = J({
|
|
226
|
+
cells: t,
|
|
179
227
|
brush: r,
|
|
180
228
|
tool: a,
|
|
181
229
|
onCellsChange: o,
|
|
182
|
-
onBrushChange:
|
|
230
|
+
onBrushChange: s,
|
|
183
231
|
onToolChange: l
|
|
184
232
|
});
|
|
185
|
-
return /* @__PURE__ */ p(
|
|
233
|
+
return /* @__PURE__ */ p(x, {
|
|
186
234
|
className: c,
|
|
187
|
-
comboStyle: (
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
235
|
+
comboStyle: (h) => {
|
|
236
|
+
const u = t[C.get(h)];
|
|
237
|
+
if (u.avail <= 0) return {
|
|
190
238
|
background: "var(--spotlab-hm-dead-hatch)",
|
|
191
239
|
backgroundColor: "var(--spotlab-hm-muted-bg)",
|
|
192
240
|
cursor: "not-allowed",
|
|
193
241
|
color: "var(--spotlab-hm-muted-fg)"
|
|
194
242
|
};
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
const
|
|
243
|
+
const f = a === "eyedropper" ? "copy" : "pointer";
|
|
244
|
+
if (u.avail < 1) {
|
|
245
|
+
const m = `${u.avail * 100}%`;
|
|
198
246
|
return {
|
|
199
|
-
background: `repeating-linear-gradient(180deg, var(--spotlab-hm-muted-fg) 0 4px, transparent 4px 8px), var(--spotlab-hm-dead-hatch), ${
|
|
200
|
-
backgroundSize: `1px 100%, ${(1 -
|
|
201
|
-
backgroundPosition: `${
|
|
247
|
+
background: `repeating-linear-gradient(180deg, var(--spotlab-hm-muted-fg) 0 4px, transparent 4px 8px), var(--spotlab-hm-dead-hatch), ${y(u, e, n)}`,
|
|
248
|
+
backgroundSize: `1px 100%, ${(1 - u.avail) * 100}% 100%, ${u.avail * 100}% 100%`,
|
|
249
|
+
backgroundPosition: `${m} center, right center, left center`,
|
|
202
250
|
backgroundRepeat: "no-repeat, no-repeat, no-repeat",
|
|
203
|
-
cursor:
|
|
251
|
+
cursor: f
|
|
204
252
|
};
|
|
205
253
|
}
|
|
206
254
|
return {
|
|
207
|
-
background:
|
|
208
|
-
cursor:
|
|
255
|
+
background: y(u, e, n),
|
|
256
|
+
cursor: f
|
|
209
257
|
};
|
|
210
258
|
},
|
|
211
|
-
|
|
212
|
-
|
|
259
|
+
onPointerDown: i,
|
|
260
|
+
onPointerEnter: d
|
|
213
261
|
});
|
|
214
|
-
},
|
|
262
|
+
}, K = (t) => /* @__PURE__ */ p("input", {
|
|
215
263
|
type: "number",
|
|
216
264
|
className: "spotlab-hm-weight-input",
|
|
217
|
-
...
|
|
218
|
-
}),
|
|
219
|
-
const a =
|
|
265
|
+
...t
|
|
266
|
+
}), V = ({ value: t, color: e, passive: n, onChange: r }) => {
|
|
267
|
+
const a = b(null), o = b(!1), s = g((l) => {
|
|
220
268
|
const c = a.current;
|
|
221
269
|
if (!c) return;
|
|
222
|
-
const
|
|
223
|
-
r((l -
|
|
270
|
+
const i = c.getBoundingClientRect();
|
|
271
|
+
r((l - i.left) / i.width * 100);
|
|
224
272
|
}, [r]);
|
|
225
|
-
return
|
|
226
|
-
const l = (
|
|
227
|
-
o.current && i
|
|
273
|
+
return N(() => {
|
|
274
|
+
const l = (i) => {
|
|
275
|
+
o.current && s(i.clientX);
|
|
228
276
|
}, c = () => o.current = !1;
|
|
229
277
|
return window.addEventListener("pointermove", l), window.addEventListener("pointerup", c), () => {
|
|
230
278
|
window.removeEventListener("pointermove", l), window.removeEventListener("pointerup", c);
|
|
231
279
|
};
|
|
232
|
-
}, [
|
|
280
|
+
}, [s]), /* @__PURE__ */ p("div", {
|
|
233
281
|
ref: a,
|
|
234
282
|
role: "slider",
|
|
235
|
-
"aria-valuenow":
|
|
283
|
+
"aria-valuenow": t,
|
|
236
284
|
"aria-valuemin": 0,
|
|
237
285
|
"aria-valuemax": 100,
|
|
238
286
|
className: "spotlab-hm-slider-track",
|
|
239
|
-
style: { background: `linear-gradient(to right, ${
|
|
287
|
+
style: { background: `linear-gradient(to right, ${e} 0 ${t}%, var(--spotlab-hm-track-bg) ${t}% 100%)` },
|
|
240
288
|
onPointerDown: (l) => {
|
|
241
|
-
o.current = !0,
|
|
289
|
+
o.current = !0, s(l.clientX);
|
|
242
290
|
},
|
|
243
291
|
children: /* @__PURE__ */ p("div", {
|
|
244
292
|
className: "spotlab-hm-slider-thumb",
|
|
245
293
|
style: {
|
|
246
|
-
left: `${
|
|
247
|
-
border: `2px solid ${n ? "var(--spotlab-hm-muted-fg)" :
|
|
294
|
+
left: `${t}%`,
|
|
295
|
+
border: `2px solid ${n ? "var(--spotlab-hm-muted-fg)" : e}`
|
|
248
296
|
}
|
|
249
297
|
})
|
|
250
298
|
});
|
|
251
|
-
},
|
|
299
|
+
}, Z = (t, e) => /* @__PURE__ */ w("span", {
|
|
252
300
|
className: "spotlab-hm-slider-label",
|
|
253
|
-
children: [
|
|
301
|
+
children: [t.label, e && /* @__PURE__ */ p("span", {
|
|
254
302
|
className: "spotlab-hm-slider-remainder",
|
|
255
303
|
children: " · remainder"
|
|
256
304
|
})]
|
|
257
|
-
}),
|
|
258
|
-
const
|
|
259
|
-
a(
|
|
305
|
+
}), st = ({ actions: t, order: e, weights: n, passive: r, onWeightsChange: a, renderActionLabel: o, renderActionExtra: s, components: l, className: c }) => {
|
|
306
|
+
const i = l?.WeightInput ?? K, d = (h, u) => {
|
|
307
|
+
a(G(n, h, u, r));
|
|
260
308
|
};
|
|
261
309
|
return /* @__PURE__ */ p("div", {
|
|
262
310
|
className: ["spotlab-hm-root spotlab-hm-sliders", c].filter(Boolean).join(" "),
|
|
263
|
-
children:
|
|
264
|
-
const
|
|
265
|
-
return /* @__PURE__ */
|
|
311
|
+
children: e.map((h) => {
|
|
312
|
+
const u = t[h], f = h === r;
|
|
313
|
+
return /* @__PURE__ */ w("div", {
|
|
266
314
|
className: "spotlab-hm-slider-row",
|
|
267
|
-
"data-passive":
|
|
268
|
-
children: [/* @__PURE__ */
|
|
315
|
+
"data-passive": f,
|
|
316
|
+
children: [/* @__PURE__ */ w("div", {
|
|
269
317
|
className: "spotlab-hm-slider-header",
|
|
270
318
|
children: [
|
|
271
319
|
/* @__PURE__ */ p("span", {
|
|
272
320
|
className: "spotlab-hm-slider-swatch",
|
|
273
|
-
style: { background:
|
|
321
|
+
style: { background: u.color }
|
|
274
322
|
}),
|
|
275
|
-
o?.(
|
|
276
|
-
|
|
323
|
+
o?.(u, h, f) ?? Z(u, f),
|
|
324
|
+
s?.(u, h)
|
|
277
325
|
]
|
|
278
|
-
}), /* @__PURE__ */
|
|
326
|
+
}), /* @__PURE__ */ w("div", {
|
|
279
327
|
className: "spotlab-hm-slider-track-row",
|
|
280
|
-
children: [/* @__PURE__ */ p(
|
|
281
|
-
value: n[
|
|
282
|
-
color:
|
|
283
|
-
passive:
|
|
284
|
-
onChange: (
|
|
285
|
-
}), /* @__PURE__ */ p(
|
|
286
|
-
value: n[
|
|
287
|
-
"aria-label": `${
|
|
288
|
-
onChange: (
|
|
328
|
+
children: [/* @__PURE__ */ p(V, {
|
|
329
|
+
value: n[h],
|
|
330
|
+
color: u.color,
|
|
331
|
+
passive: f,
|
|
332
|
+
onChange: (m) => d(h, m)
|
|
333
|
+
}), /* @__PURE__ */ p(i, {
|
|
334
|
+
value: n[h],
|
|
335
|
+
"aria-label": `${u.label} weight`,
|
|
336
|
+
onChange: (m) => d(h, parseInt(m.target.value || "0", 10))
|
|
289
337
|
})]
|
|
290
338
|
})]
|
|
291
|
-
},
|
|
339
|
+
}, u.id);
|
|
292
340
|
})
|
|
293
341
|
});
|
|
294
342
|
};
|
|
295
343
|
export {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
344
|
+
st as ActionWeightSliders,
|
|
345
|
+
T as CELL_FILL_DIRECTION,
|
|
346
|
+
x as HandMatrix,
|
|
347
|
+
rt as RangeGrid,
|
|
348
|
+
lt as RangeMatrixEditor,
|
|
349
|
+
R as cellFillGradient,
|
|
350
|
+
H as cellsToComboWeights,
|
|
351
|
+
ot as cellsToRanges,
|
|
352
|
+
at as comboWeightsToCells,
|
|
353
|
+
G as rebalance,
|
|
354
|
+
nt as seedCells,
|
|
355
|
+
J as useRangePainting
|
|
307
356
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spotlab/hand-matrix",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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,19 +40,19 @@
|
|
|
40
40
|
"LICENSE"
|
|
41
41
|
],
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@spotlab/range": "^0.
|
|
43
|
+
"@spotlab/range": "^0.3.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.3.0",
|
|
49
49
|
"@storybook/addon-vitest": "^10.5.5",
|
|
50
50
|
"@storybook/react-vite": "^10.5.5",
|
|
51
51
|
"@types/react": "^19.2.16",
|
|
52
52
|
"@types/react-dom": "^19.2.3",
|
|
53
53
|
"@vitejs/plugin-react": "^6.0.0",
|
|
54
|
-
"@vitest/browser": "^
|
|
55
|
-
"@vitest/browser-playwright": "
|
|
54
|
+
"@vitest/browser": "^5.0.0",
|
|
55
|
+
"@vitest/browser-playwright": "^5.0.0",
|
|
56
56
|
"playwright": "^1.49.1",
|
|
57
57
|
"react": "^19.2.7",
|
|
58
58
|
"react-dom": "^19.2.7",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"typescript": "^6.0.0",
|
|
61
61
|
"vite": "^8.3.1",
|
|
62
62
|
"vite-plugin-dts": "^5.1.2",
|
|
63
|
-
"vitest": "^
|
|
63
|
+
"vitest": "^5.0.0"
|
|
64
64
|
},
|
|
65
65
|
"publishConfig": {
|
|
66
66
|
"access": "public"
|
|
@@ -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
|
}
|