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