@spotlab/hand-matrix 0.3.0 → 0.5.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
@@ -5,13 +5,13 @@ and [`@spotlab/range`](../range) — no Tailwind, no bundler-plugin
5
5
  requirement.
6
6
 
7
7
  ```tsx
8
- import { RangeGrid } from "@spotlab/hand-matrix";
8
+ import { RangeMatrix, rangeToMatrix } from "@spotlab/hand-matrix";
9
9
  import "@spotlab/hand-matrix/style.css";
10
10
  import { initRange } from "@spotlab/range";
11
11
 
12
12
  await initRange(); // once, at startup
13
13
 
14
- <RangeGrid range="AKs:0.5,QQ" />;
14
+ <RangeMatrix {...rangeToMatrix("AKs:0.5,QQ")} />;
15
15
  ```
16
16
 
17
17
  ## Exports
@@ -20,31 +20,33 @@ Everything below is exported from the package root.
20
20
 
21
21
  ### Display
22
22
 
23
- - `HandMatrix` — the bare 13x13 grid primitive. Takes no range data; styling
24
- is entirely the caller's job via `comboStyle`, keyed by combo string (e.g.
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.
37
- - `RangeGrid` — a read-only display component. Accepts the expanded
38
- `HandFrequency[]` form or a range string directly (parsed via
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.
45
-
46
- `RangeGrid`'s combo collapse (`toComboWeights`) is a wasm call, so
47
- `initRange()` must have resolved before rendering it — see
23
+ - `RangeMatrix` — the read-only grid, for one range or several actions at
24
+ once, drawn exactly as `RangeMatrixEditor` draws it but without painting.
25
+ Each cell stacks its actions' weights as colored bands, dead cells
26
+ (`avail` 0) are hatched, and capped cells (`avail` below 1) show a cap
27
+ line with the rest hatched. Each cell carries a `data-combo` attribute.
28
+ Props (`RangeMatrixProps`):
29
+ - `cells: EditorCell[]` — one per combo, in `@spotlab/range`'s `COMBOS`
30
+ order (see [Editing](#editing)).
31
+ - `actions: EditorActionSpec[]` — index-aligned with each cell's
32
+ `weights`.
33
+ - `order: number[]` — indices into `actions`, in the order bands stack.
34
+ An action left out isn't drawn, so its share of a cell stays
35
+ transparent; include it to give it a color.
36
+ - `className?: string`.
37
+ - `rangeToMatrix(range, { color?, labels? }): RangeMatrixProps` (without
38
+ `className`) — `RangeMatrix`'s props for drawing one range:
39
+ `<RangeMatrix {...rangeToMatrix(range)} />`. Takes a range string (parsed
40
+ via `@spotlab/range`) or the expanded `HandFrequency[]` form, and fills
41
+ each cell to its combo's mean weight in `color` (default
42
+ `var(--spotlab-hm-fill)`), leaving the rest blank. The returned `actions`
43
+ are `in` and `out`, labeled by `labels.in` (default `"In range"`) and
44
+ `labels.out` (default `"Out of range"`), for building your own legend.
45
+ Memoize the result (e.g. `useMemo`) rather than calling it on every
46
+ render.
47
+
48
+ `rangeToMatrix`'s parse and combo collapse (`toComboWeights`) are wasm
49
+ calls, so `initRange()` must have resolved before calling it — see
48
50
  [`@spotlab/range`](../range)'s own README for why that call is explicit
49
51
  rather than automatic.
50
52
 
@@ -55,9 +57,8 @@ raise). Its state is an array of 169 `EditorCell`s, one per combo in
55
57
  `@spotlab/range`'s `COMBOS` order. Every component is fully controlled: state
56
58
  goes in as props and changes come out through `on*Change` callbacks.
57
59
 
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.
60
+ - `RangeMatrixEditor` — the paintable grid: `RangeMatrix`'s cells with
61
+ brush/eyedropper painting wired on for mouse and touch.
61
62
  Props (`RangeMatrixEditorProps`):
62
63
  - `cells: EditorCell[]` and `onCellsChange(cells)`.
63
64
  - `actions: EditorActionSpec[]` — index-aligned with each cell's
@@ -85,9 +86,9 @@ goes in as props and changes come out through `on*Change` callbacks.
85
86
  - `useRangePainting(opts)` — the brush/eyedropper state machine behind
86
87
  `RangeMatrixEditor`, for building your own grid. `opts` takes the editor's
87
88
  `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`.
89
+ `{ onCellDown(combo), onCellEnter(combo) }`: call `onCellDown` when a
90
+ pointer goes down on a cell and `onCellEnter` each time it moves onto
91
+ another one. A stroke ends on any window `pointerup` or `pointercancel`.
91
92
  - `seedCells(avail, actionWeights, passive): EditorCell[]` — build the
92
93
  initial grid from a parent range's per-combo availability (`null` for
93
94
  unconstrained) and each action's absolute per-combo weights (the
@@ -95,13 +96,16 @@ goes in as props and changes come out through `on*Change` callbacks.
95
96
  produces).
96
97
  - `cellsToComboWeights(cells, actionIndex): Map<string, number>` — collapse
97
98
  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.
99
+ - `cellsToRanges(original, seed, cells, avail?): HandFrequency[][]` — turn
100
+ the edited grid back into one hand-keyed range per action. `original`
101
+ holds each action's range (or `null`) in editor action order, `seed` the
102
+ cells the editor opened with, and `cells` the cells after editing.
103
+ `avail` is the parent range per hand (`null` for unconstrained); leave it
104
+ out when the parent gives every hand of a combo the same weight. Combos
105
+ whose cell still matches its seed keep each action's original hands
106
+ verbatim, and a `null` action takes what's left of each hand there.
107
+ Changed combos are rebuilt from the cells. Either way, each hand's
108
+ actions sum to its parent weight.
105
109
  - `comboWeightsToCells(cells, actionWeights, passive): EditorCell[]` — the
106
110
  inverse: re-derive the cells' conditional weights from absolute
107
111
  per-action ranges, keeping each cell's `avail`.
@@ -112,7 +116,7 @@ goes in as props and changes come out through `on*Change` callbacks.
112
116
  is the combo's cap in the parent range and `weights` are each action's
113
117
  share of it, summing to 100), `EditorActionSpec`
114
118
  (`{ id: string; label: string; color: string }`), `PaintTool`,
115
- `CellPointerHandler`, `RangeMatrixEditorProps`, `ActionWeightSlidersProps`,
119
+ `RangeMatrixEditorProps`, `ActionWeightSlidersProps`,
116
120
  `WeightInputProps`.
117
121
 
118
122
  **The editor works at combo level.** A cell is one of the 169 combos (`AKs`,
@@ -130,7 +134,7 @@ custom property has a default declared on `.spotlab-hm-root`, so the
130
134
  component renders correctly with zero theming.
131
135
 
132
136
  Because the `className` you pass to a component lands on that same root, a
133
- class that sets variables works there too (`<RangeGrid className="my-theme" />`
137
+ class that sets variables works there too (`<RangeMatrix className="my-theme" … />`
134
138
  with `.my-theme { --spotlab-hm-border: … }`), in addition to the
135
139
  `.spotlab-hm-root` selector form below.
136
140
 
@@ -192,9 +196,10 @@ Available variables: `--spotlab-hm-border`, `--spotlab-hm-border-width`,
192
196
  `--spotlab-hm-muted-bg`, `--spotlab-hm-muted-fg`, `--spotlab-hm-track-bg`,
193
197
  `--spotlab-hm-row-passive-bg`, `--spotlab-hm-dead-hatch`.
194
198
 
195
- Action/fill colors (`RangeGrid`'s `fill` prop) are **not** theme variables —
196
- they are data, passed as props. `--spotlab-hm-fill` is only the default used
197
- when `fill` is omitted.
199
+ Action colors (each `EditorActionSpec`'s `color`, or `rangeToMatrix`'s
200
+ `color` option) are **not** theme variables — they are data, passed as props.
201
+ `--spotlab-hm-fill` is only the default `rangeToMatrix` uses when `color` is
202
+ omitted.
198
203
 
199
204
  ## License
200
205
 
@@ -31,17 +31,23 @@ export declare function comboWeightsToCells(cells: EditorCell[], actionWeights:
31
31
  * Turn an edited grid back into one hand-keyed range per action, keeping each
32
32
  * action's original hands wherever the editor didn't touch them. `original`
33
33
  * holds each action's range in editor action order, `seed` the cells the
34
- * editor opened with, and `cells` the cells after editing.
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.
35
39
  *
36
40
  * A combo whose cell still equals its seed cell (same `avail`, same weights)
37
41
  * keeps every action's original hands verbatim, so input the whole-percent grid
38
42
  * 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.
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.
42
48
  *
43
49
  * Each rebuilt combo takes its first original hand's place, and combos the
44
50
  * original didn't have follow in matrix order, so an unedited range formats
45
51
  * back to the string it came from.
46
52
  */
47
- export declare function cellsToRanges(original: (HandFrequency[] | null)[], seed: EditorCell[], cells: EditorCell[]): HandFrequency[][];
53
+ export declare function cellsToRanges(original: (HandFrequency[] | null)[], seed: EditorCell[], cells: EditorCell[], avail?: HandFrequency[] | null): HandFrequency[][];
@@ -1,24 +1,17 @@
1
1
  import { FC } from 'react';
2
+ import { MatrixDisplayProps } from '../hand-matrix';
2
3
  import { PaintTool } from './use-range-painting';
3
4
  import { EditorCell } from './cells';
4
- import { EditorActionSpec } from './weights';
5
- export type RangeMatrixEditorProps = {
6
- /** The 169-cell grid, in `@spotlab/range`'s `COMBOS` order. */
7
- cells: EditorCell[];
8
- /** Index-aligned with each cell's `weights` array. */
9
- actions: EditorActionSpec[];
10
- /** Indices into `actions`/`weights`, in the order bands should stack — a display concern the consumer owns (e.g. biggest raise first). */
11
- order: number[];
5
+ export type RangeMatrixEditorProps = MatrixDisplayProps & {
12
6
  brush: number[];
13
7
  tool: PaintTool;
14
8
  onCellsChange: (cells: EditorCell[]) => void;
15
9
  onBrushChange: (brush: number[]) => void;
16
10
  onToolChange: (tool: PaintTool) => void;
17
- className?: string;
18
11
  };
19
12
  /**
20
- * The paintable 13x13 range grid: per-cell action bands, the dead-cell hatch,
21
- * the capped-cell cap marker, and the brush/eyedropper painting wired onto
22
- * `HandMatrix`. Fully controlled; `useRangePainting` drives the state changes.
13
+ * The paintable 13x13 range grid: `RangeMatrix`'s cells with brush/eyedropper
14
+ * painting wired onto them. Fully controlled; `useRangePainting` drives the
15
+ * state changes.
23
16
  */
24
17
  export declare const RangeMatrixEditor: FC<RangeMatrixEditorProps>;
@@ -1,10 +1,7 @@
1
1
  import { CSSProperties, FC, PointerEvent as ReactPointerEvent } from 'react';
2
- export declare const CELL_FILL_DIRECTION = "to right";
3
- export declare const cellFillGradient: (stops: string[]) => string;
4
- export type CellPointerHandler = (combo: string, event: ReactPointerEvent<HTMLDivElement>) => void;
5
- type ComboStyleProp = {
6
- comboStyle?: (combo: string) => CSSProperties;
7
- };
2
+ import { EditorCell } from './editor/cells';
3
+ import { EditorActionSpec } from './editor/weights';
4
+ type CellPointerHandler = (combo: string, event: ReactPointerEvent<HTMLDivElement>) => void;
8
5
  type GridPointerHandlers = {
9
6
  /**
10
7
  * A pointer (mouse, pen or touch) went down on `combo`'s cell. The grid
@@ -22,12 +19,29 @@ type GridPointerHandlers = {
22
19
  */
23
20
  onPointerEnter?: CellPointerHandler;
24
21
  };
22
+ /** The cell data both `RangeMatrix` and `RangeMatrixEditor` draw from. */
23
+ export type MatrixDisplayProps = {
24
+ /** The 169-cell grid, in `@spotlab/range`'s `COMBOS` order. */
25
+ cells: EditorCell[];
26
+ /** Index-aligned with each cell's `weights` array. */
27
+ actions: EditorActionSpec[];
28
+ /**
29
+ * Indices into `actions`/`weights`, in the order bands should stack. An
30
+ * action left out isn't drawn, so its share of a cell stays transparent.
31
+ */
32
+ order: number[];
33
+ className?: string;
34
+ };
25
35
  /**
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.
36
+ * The 13x13 grid that `RangeMatrix` and `RangeMatrixEditor` both render, so
37
+ * the two draw cells identically: each cell stacks its actions' weights as
38
+ * bands in `order`, with dead and capped cells marked. Each cell carries a
39
+ * `data-combo` attribute. The editor adds a per-cell cursor and the painting
40
+ * handlers; passing `onPointerDown` also makes the grid a drag surface
41
+ * (`dragSurfaceStyle`), so a drag reaches the handlers instead of scrolling
42
+ * the page or selecting text. Internal: not exported from the package root.
29
43
  */
30
- export declare const HandMatrix: FC<{
31
- className?: string;
32
- } & ComboStyleProp & GridPointerHandlers>;
44
+ export declare const HandMatrix: FC<MatrixDisplayProps & GridPointerHandlers & {
45
+ cursorOf?: (cell: EditorCell) => CSSProperties["cursor"];
46
+ }>;
33
47
  export {};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { HandMatrix, cellFillGradient, CELL_FILL_DIRECTION, type CellPointerHandler, } from './hand-matrix';
2
- export { RangeGrid } from './range-grid';
1
+ export { RangeMatrix, type RangeMatrixProps } from './range-matrix';
2
+ export { rangeToMatrix } from './range-to-matrix';
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';
package/dist/index.js CHANGED
@@ -1,356 +1,368 @@
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", {
11
- className: "spotlab-hm-cell",
12
- "data-combo": t,
13
- style: n?.(t),
14
- onPointerDown: e && ((r) => {
15
- O(r), e(t, r);
16
- }),
17
- children: /* @__PURE__ */ p("div", {
18
- className: "spotlab-hm-cell-inner",
19
- children: /* @__PURE__ */ p("div", {
20
- "data-combo": t,
21
- children: t
22
- })
23
- })
24
- }), F = ({ row: t, ...e }) => /* @__PURE__ */ p("div", {
25
- className: "spotlab-hm-row",
26
- children: t.map((n, r) => /* @__PURE__ */ p(B, {
27
- combo: n,
28
- ...e
29
- }, r))
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, {
60
- className: n,
61
- comboStyle: (a) => {
62
- const o = r.get(a);
63
- return o ? _(e, o) : {};
64
- }
65
- });
66
- }, j = (t) => Math.max(0, Math.min(100, Math.round(t))), q = (t, e) => {
1
+ import { useCallback as M, useEffect as R, useLayoutEffect as L, useRef as y } from "react";
2
+ import { COMBOS as C, comboOf as P, handsOfCombo as T, parseRange as W, toComboWeights as D } from "@spotlab/range";
3
+ import { jsx as v, jsxs as E } from "react/jsx-runtime";
4
+ var B = (t) => Math.max(0, Math.min(100, Math.round(t))), F = (t, r) => {
67
5
  const n = t.map(Math.round);
68
- let r = 100 - n.reduce((a, o) => a + o, 0);
69
- if (r !== 0) {
70
- for (let a = 0; a < n.length; a++) if (a !== e && n[a] + r >= 0) {
71
- n[a] += r, r = 0;
6
+ let e = 100 - n.reduce((a, o) => a + o, 0);
7
+ if (e !== 0) {
8
+ for (let a = 0; a < n.length; a++) if (a !== r && n[a] + e >= 0) {
9
+ n[a] += e, e = 0;
72
10
  break;
73
11
  }
74
- if (r !== 0) {
75
- for (let a = 0; a < n.length; a++) if (a !== e && n[a] > 0) {
76
- const o = Math.min(n[a], -r);
77
- if (n[a] -= o, r += o, r === 0) break;
12
+ if (e !== 0) {
13
+ for (let a = 0; a < n.length; a++) if (a !== r && n[a] > 0) {
14
+ const o = Math.min(n[a], -e);
15
+ if (n[a] -= o, e += o, e === 0) break;
78
16
  }
79
- n[e] = Math.max(0, n[e] + r);
17
+ n[r] = Math.max(0, n[r] + e);
80
18
  }
81
19
  }
82
20
  return n;
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);
89
- if (c <= 0) {
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) => {
21
+ }, X = (t, r, n, e) => {
22
+ const a = B(n), o = t.slice();
23
+ o[r] = a;
24
+ const c = 100 - a, i = [];
25
+ for (let l = 0; l < t.length; l++) l !== r && i.push(l);
26
+ const u = i.reduce((l, f) => l + t[f], 0);
27
+ if (u <= 0) {
28
+ i.forEach((f) => o[f] = 0);
29
+ const l = r === e ? i[0] : e;
30
+ l != null && (o[l] = c);
31
+ } else i.forEach((l) => o[l] = Math.round(t[l] / u * c));
32
+ return F(o, r);
33
+ }, N = (t, r) => t.reduce((n, e, a) => a === r ? n : n + e, 0), k = new Map(C.map((t, r) => [t, r])), j = (t, r, n) => {
34
+ const e = t.slice();
35
+ return e[k.get(r)] = n, e;
36
+ }, I = (t, r) => {
99
37
  const n = t.slice();
100
- let r = E(n, e);
101
- if (r > 100) {
102
- const a = 100 / r;
103
- for (let o = 0; o < n.length; o++) o !== e && (n[o] = Math.round(n[o] * a));
104
- r = E(n, e);
38
+ let e = N(n, r);
39
+ if (e > 100) {
40
+ const a = 100 / e;
41
+ for (let o = 0; o < n.length; o++) o !== r && (n[o] = Math.round(n[o] * a));
42
+ e = N(n, r);
105
43
  }
106
- return n[e] = Math.max(0, 100 - r), n;
44
+ return n[r] = Math.max(0, 100 - e), n;
107
45
  };
108
- function nt(t, e, n) {
109
- return v.map((r) => {
110
- const a = t ? t.get(r) ?? 0 : 1, o = e.map(() => 0);
46
+ function et(t, r, n) {
47
+ return C.map((e) => {
48
+ const a = t ? t.get(e) ?? 0 : 1, o = r.map(() => 0);
111
49
  return a <= 0 ? (o[n] = 100, {
112
50
  avail: 0,
113
51
  weights: o
114
- }) : (e.forEach((s, l) => {
115
- const c = s?.get(r) ?? 0;
116
- o[l] = Math.round(c / a * 100);
52
+ }) : (r.forEach((c, i) => {
53
+ const u = c?.get(e) ?? 0;
54
+ o[i] = Math.round(u / a * 100);
117
55
  }), {
118
56
  avail: a,
119
- weights: L(o, n)
57
+ weights: I(o, n)
120
58
  });
121
59
  });
122
60
  }
123
- var Y = 8;
124
- function H(t, e) {
61
+ var S = 8;
62
+ function rt(t, r) {
125
63
  const n = /* @__PURE__ */ new Map();
126
- return t.forEach((r, a) => {
127
- if (r.avail <= 0) return;
128
- const o = r.weights.reduce((l, c) => l + c, 0);
64
+ return t.forEach((e, a) => {
65
+ if (e.avail <= 0) return;
66
+ const o = e.weights.reduce((i, u) => i + u, 0);
129
67
  if (!o) return;
130
- const s = Number((r.weights[e] / o * r.avail).toFixed(Y));
131
- s && n.set(v[a], s);
68
+ const c = Number((e.weights[r] / o * e.avail).toFixed(S));
69
+ c && n.set(C[a], c);
132
70
  }), n;
133
71
  }
134
- function at(t, e, n) {
135
- return t.map((r, a) => {
136
- const o = e.map(() => 0);
137
- if (r.avail <= 0)
72
+ function nt(t, r, n) {
73
+ return t.map((e, a) => {
74
+ const o = r.map(() => 0);
75
+ if (e.avail <= 0)
138
76
  return o[n] = 100, {
139
- avail: r.avail,
77
+ avail: e.avail,
140
78
  weights: o
141
79
  };
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);
80
+ const c = C[a];
81
+ return r.forEach((i, u) => {
82
+ const l = i?.get(c) ?? 0;
83
+ o[u] = Math.round(l / e.avail * 100);
146
84
  }), {
147
- avail: r.avail,
148
- weights: L(o, n)
85
+ avail: e.avail,
86
+ weights: I(o, n)
149
87
  };
150
88
  });
151
89
  }
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);
90
+ var q = (t, r) => t.avail !== r.avail || t.weights.length !== r.weights.length || t.weights.some((n, e) => n !== r.weights[e]);
91
+ function at(t, r, n, e) {
92
+ const a = e && new Map(e.map(({ hand: s, frequency: h }) => [s, h])), o = (s, h) => e === void 0 ? h.avail : a ? a.get(s) ?? 0 : 1, c = t.map((s) => s && new Map(s.map(({ hand: h, frequency: g }) => [h, g]))), i = (s) => c.reduce((h, g) => h + (g?.get(s) ?? 0), 0), u = t.flatMap((s, h) => s ? [] : [h]), l = (s, h) => T(s).flatMap((g) => {
93
+ const b = Number(h(g).toFixed(S));
94
+ return b > 0 ? [{
95
+ hand: g,
96
+ frequency: b
97
+ }] : [];
98
+ }), f = (s, h) => {
99
+ const g = n[k.get(s)], b = g.weights.reduce((m, w) => m + w, 0);
100
+ return g.avail <= 0 || !b ? [] : l(s, (m) => g.weights[h] / b * o(m, g));
101
+ }, p = (s, h) => {
102
+ const g = n[k.get(s)], b = u.reduce((w, O) => w + g.weights[O], 0), m = b ? g.weights[h] / b : 1 / u.length;
103
+ return l(s, (w) => Math.max(0, o(w, g) - i(w)) * m);
104
+ }, d = new Set(C.filter((s, h) => q(r[h], n[h])));
105
+ return t.map((s, h) => {
106
+ if (s === null) return C.flatMap((m) => d.has(m) ? f(m, h) : p(m, h));
107
+ const g = [], b = /* @__PURE__ */ new Set();
108
+ for (const m of s) {
109
+ const w = P(m.hand);
110
+ d.has(w) ? b.has(w) || (b.add(w), g.push(...f(w, h))) : g.push(m);
165
111
  }
166
- for (const d of r) i.has(d) || c.push(...l(d));
167
- return c;
112
+ for (const m of d) b.has(m) || g.push(...f(m, h));
113
+ return g;
114
+ });
115
+ }
116
+ var _ = (t, r) => Array.from({ length: Math.ceil(t.length / r) }, (n, e) => t.slice(e * r, e * r + r)), U = (t) => `linear-gradient(to right, ${t.join(", ")})`, $ = (t, r, n) => {
117
+ const e = [];
118
+ let a = 0;
119
+ return n.forEach((o) => {
120
+ const c = (t.weights[o] || 0) / 100;
121
+ if (c <= 0) return;
122
+ const i = r[o].color;
123
+ e.push(`${i} ${a * 100}%`), e.push(`${i} ${(a + c) * 100}%`), a += c;
124
+ }), e.length > 0 && a < 1 - 1e-9 && e.push(`transparent ${a * 100}% 100%`), e.length > 0 ? U(e) : "none";
125
+ }, Y = (t, r, n) => {
126
+ if (t.avail <= 0) return {
127
+ backgroundImage: "var(--spotlab-hm-dead-hatch)",
128
+ backgroundColor: "var(--spotlab-hm-muted-bg)",
129
+ color: "var(--spotlab-hm-muted-fg)"
130
+ };
131
+ if (t.avail < 1) {
132
+ const e = `${t.avail * 100}%`;
133
+ return {
134
+ backgroundImage: `repeating-linear-gradient(180deg, var(--spotlab-hm-muted-fg) 0 4px, transparent 4px 8px), var(--spotlab-hm-dead-hatch), ${$(t, r, n)}`,
135
+ backgroundSize: `1px 100%, ${(1 - t.avail) * 100}% 100%, ${t.avail * 100}% 100%`,
136
+ backgroundPosition: `${e} center, right center, left center`,
137
+ backgroundRepeat: "no-repeat, no-repeat, no-repeat"
138
+ };
139
+ }
140
+ return { backgroundImage: $(t, r, n) };
141
+ }, G = (t) => {
142
+ const r = t.target;
143
+ r.hasPointerCapture(t.pointerId) && r.releasePointerCapture(t.pointerId);
144
+ }, x = (t, r, n) => {
145
+ const e = t.ownerDocument.elementFromPoint(r, n)?.closest("[data-combo]");
146
+ return !e || !t.contains(e) ? null : e.getAttribute("data-combo");
147
+ }, H = {
148
+ touchAction: "none",
149
+ userSelect: "none",
150
+ WebkitUserSelect: "none",
151
+ WebkitTouchCallout: "none"
152
+ }, A = ({ cells: t, actions: r, order: n, className: e, cursorOf: a, onPointerDown: o, onPointerEnter: c }) => {
153
+ const i = y(null), u = (l) => {
154
+ const f = x(l.currentTarget, l.clientX, l.clientY);
155
+ f !== i.current && (i.current = f, f && c?.(f, l));
156
+ };
157
+ return /* @__PURE__ */ v("div", {
158
+ className: ["spotlab-hm-root", e].filter(Boolean).join(" "),
159
+ style: o ? H : void 0,
160
+ onPointerDown: c && ((l) => {
161
+ i.current = x(l.currentTarget, l.clientX, l.clientY);
162
+ }),
163
+ onPointerMove: c && u,
164
+ onPointerLeave: c && (() => i.current = null),
165
+ children: _(C, 13).map((l, f) => /* @__PURE__ */ v("div", {
166
+ className: "spotlab-hm-row",
167
+ children: l.map((p) => {
168
+ const d = t[k.get(p)];
169
+ return /* @__PURE__ */ v("div", {
170
+ className: "spotlab-hm-cell",
171
+ "data-combo": p,
172
+ style: {
173
+ ...Y(d, r, n),
174
+ cursor: a?.(d)
175
+ },
176
+ onPointerDown: o && ((s) => {
177
+ G(s), o(p, s);
178
+ }),
179
+ children: /* @__PURE__ */ v("div", {
180
+ className: "spotlab-hm-cell-inner",
181
+ children: /* @__PURE__ */ v("div", {
182
+ "data-combo": p,
183
+ children: p
184
+ })
185
+ })
186
+ }, p);
187
+ })
188
+ }, f))
168
189
  });
190
+ }, ot = A;
191
+ function lt(t, { color: r = "var(--spotlab-hm-fill)", labels: n = {} } = {}) {
192
+ const e = D(typeof t == "string" ? W(t) : t);
193
+ return {
194
+ cells: C.map((a) => {
195
+ const o = (e.get(a) ?? 0) * 100;
196
+ return {
197
+ avail: 1,
198
+ weights: [o, 100 - o]
199
+ };
200
+ }),
201
+ actions: [{
202
+ id: "in",
203
+ label: n.in ?? "In range",
204
+ color: r
205
+ }, {
206
+ id: "out",
207
+ label: n.out ?? "Out of range",
208
+ color: "transparent"
209
+ }],
210
+ order: [0]
211
+ };
169
212
  }
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;
213
+ function Q(t) {
214
+ const { cells: r, brush: n, tool: e, onCellsChange: a, onBrushChange: o, onToolChange: c } = t, i = y(null), u = M(() => {
215
+ i.current?.abort(), i.current = null;
173
216
  }, []);
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,
217
+ R(() => u, [u]);
218
+ const l = y(r);
219
+ L(() => {
220
+ l.current = r;
221
+ }, [r]);
222
+ const f = M((d) => {
223
+ const s = l.current, { avail: h } = s[k.get(d)];
224
+ if (h <= 0) return;
225
+ const g = j(s, d, {
226
+ avail: h,
184
227
  weights: [...n]
185
228
  });
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"));
229
+ l.current = g, a(g);
230
+ }, [n, a]), p = M((d) => {
231
+ const s = k.get(d);
232
+ r[s].avail <= 0 || (o([...r[s].weights]), c("brush"));
190
233
  }, [
191
- e,
234
+ r,
192
235
  o,
193
- s
236
+ c
194
237
  ]);
195
238
  return {
196
- onCellDown: g((u) => {
197
- if (r === "eyedropper") {
198
- h(u);
239
+ onCellDown: M((d) => {
240
+ if (e === "eyedropper") {
241
+ p(d);
199
242
  return;
200
243
  }
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);
244
+ u();
245
+ const s = new AbortController();
246
+ i.current = s, window.addEventListener("pointerup", u, { signal: s.signal }), window.addEventListener("pointercancel", u, { signal: s.signal }), f(d);
204
247
  }, [
205
- r,
206
- h,
207
- d,
208
- c
248
+ e,
249
+ p,
250
+ f,
251
+ u
209
252
  ]),
210
- onCellEnter: g((u) => {
211
- l.current && r === "brush" && d(u);
212
- }, [r, d])
253
+ onCellEnter: M((d) => {
254
+ i.current && e === "brush" && f(d);
255
+ }, [e, f])
213
256
  };
214
257
  }
215
- var y = (t, e, n) => {
216
- const r = [];
217
- let a = 0;
218
- return n.forEach((o) => {
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({
258
+ var st = ({ cells: t, actions: r, order: n, brush: e, tool: a, onCellsChange: o, onBrushChange: c, onToolChange: i, className: u }) => {
259
+ const { onCellDown: l, onCellEnter: f } = Q({
226
260
  cells: t,
227
- brush: r,
261
+ brush: e,
228
262
  tool: a,
229
263
  onCellsChange: o,
230
- onBrushChange: s,
231
- onToolChange: l
264
+ onBrushChange: c,
265
+ onToolChange: i
232
266
  });
233
- return /* @__PURE__ */ p(x, {
234
- className: c,
235
- comboStyle: (h) => {
236
- const u = t[C.get(h)];
237
- if (u.avail <= 0) return {
238
- background: "var(--spotlab-hm-dead-hatch)",
239
- backgroundColor: "var(--spotlab-hm-muted-bg)",
240
- cursor: "not-allowed",
241
- color: "var(--spotlab-hm-muted-fg)"
242
- };
243
- const f = a === "eyedropper" ? "copy" : "pointer";
244
- if (u.avail < 1) {
245
- const m = `${u.avail * 100}%`;
246
- return {
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`,
250
- backgroundRepeat: "no-repeat, no-repeat, no-repeat",
251
- cursor: f
252
- };
253
- }
254
- return {
255
- background: y(u, e, n),
256
- cursor: f
257
- };
258
- },
259
- onPointerDown: i,
260
- onPointerEnter: d
267
+ return /* @__PURE__ */ v(A, {
268
+ cells: t,
269
+ actions: r,
270
+ order: n,
271
+ className: u,
272
+ cursorOf: (p) => p.avail <= 0 ? "not-allowed" : a === "eyedropper" ? "copy" : "pointer",
273
+ onPointerDown: l,
274
+ onPointerEnter: f
261
275
  });
262
- }, K = (t) => /* @__PURE__ */ p("input", {
276
+ }, J = (t) => /* @__PURE__ */ v("input", {
263
277
  type: "number",
264
278
  className: "spotlab-hm-weight-input",
265
279
  ...t
266
- }), V = ({ value: t, color: e, passive: n, onChange: r }) => {
267
- const a = b(null), o = b(!1), s = g((l) => {
268
- const c = a.current;
269
- if (!c) return;
270
- const i = c.getBoundingClientRect();
271
- r((l - i.left) / i.width * 100);
272
- }, [r]);
273
- return N(() => {
274
- const l = (i) => {
275
- o.current && s(i.clientX);
276
- }, c = () => o.current = !1;
277
- return window.addEventListener("pointermove", l), window.addEventListener("pointerup", c), () => {
278
- window.removeEventListener("pointermove", l), window.removeEventListener("pointerup", c);
280
+ }), K = ({ value: t, color: r, passive: n, onChange: e }) => {
281
+ const a = y(null), o = y(!1), c = M((i) => {
282
+ const u = a.current;
283
+ if (!u) return;
284
+ const l = u.getBoundingClientRect();
285
+ e((i - l.left) / l.width * 100);
286
+ }, [e]);
287
+ return R(() => {
288
+ const i = (l) => {
289
+ o.current && c(l.clientX);
290
+ }, u = () => o.current = !1;
291
+ return window.addEventListener("pointermove", i), window.addEventListener("pointerup", u), () => {
292
+ window.removeEventListener("pointermove", i), window.removeEventListener("pointerup", u);
279
293
  };
280
- }, [s]), /* @__PURE__ */ p("div", {
294
+ }, [c]), /* @__PURE__ */ v("div", {
281
295
  ref: a,
282
296
  role: "slider",
283
297
  "aria-valuenow": t,
284
298
  "aria-valuemin": 0,
285
299
  "aria-valuemax": 100,
286
300
  className: "spotlab-hm-slider-track",
287
- style: { background: `linear-gradient(to right, ${e} 0 ${t}%, var(--spotlab-hm-track-bg) ${t}% 100%)` },
288
- onPointerDown: (l) => {
289
- o.current = !0, s(l.clientX);
301
+ style: { background: `linear-gradient(to right, ${r} 0 ${t}%, var(--spotlab-hm-track-bg) ${t}% 100%)` },
302
+ onPointerDown: (i) => {
303
+ o.current = !0, c(i.clientX);
290
304
  },
291
- children: /* @__PURE__ */ p("div", {
305
+ children: /* @__PURE__ */ v("div", {
292
306
  className: "spotlab-hm-slider-thumb",
293
307
  style: {
294
308
  left: `${t}%`,
295
- border: `2px solid ${n ? "var(--spotlab-hm-muted-fg)" : e}`
309
+ border: `2px solid ${n ? "var(--spotlab-hm-muted-fg)" : r}`
296
310
  }
297
311
  })
298
312
  });
299
- }, Z = (t, e) => /* @__PURE__ */ w("span", {
313
+ }, V = (t, r) => /* @__PURE__ */ E("span", {
300
314
  className: "spotlab-hm-slider-label",
301
- children: [t.label, e && /* @__PURE__ */ p("span", {
315
+ children: [t.label, r && /* @__PURE__ */ v("span", {
302
316
  className: "spotlab-hm-slider-remainder",
303
317
  children: " · remainder"
304
318
  })]
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));
319
+ }), it = ({ actions: t, order: r, weights: n, passive: e, onWeightsChange: a, renderActionLabel: o, renderActionExtra: c, components: i, className: u }) => {
320
+ const l = i?.WeightInput ?? J, f = (p, d) => {
321
+ a(X(n, p, d, e));
308
322
  };
309
- return /* @__PURE__ */ p("div", {
310
- className: ["spotlab-hm-root spotlab-hm-sliders", c].filter(Boolean).join(" "),
311
- children: e.map((h) => {
312
- const u = t[h], f = h === r;
313
- return /* @__PURE__ */ w("div", {
323
+ return /* @__PURE__ */ v("div", {
324
+ className: ["spotlab-hm-root spotlab-hm-sliders", u].filter(Boolean).join(" "),
325
+ children: r.map((p) => {
326
+ const d = t[p], s = p === e;
327
+ return /* @__PURE__ */ E("div", {
314
328
  className: "spotlab-hm-slider-row",
315
- "data-passive": f,
316
- children: [/* @__PURE__ */ w("div", {
329
+ "data-passive": s,
330
+ children: [/* @__PURE__ */ E("div", {
317
331
  className: "spotlab-hm-slider-header",
318
332
  children: [
319
- /* @__PURE__ */ p("span", {
333
+ /* @__PURE__ */ v("span", {
320
334
  className: "spotlab-hm-slider-swatch",
321
- style: { background: u.color }
335
+ style: { background: d.color }
322
336
  }),
323
- o?.(u, h, f) ?? Z(u, f),
324
- s?.(u, h)
337
+ o?.(d, p, s) ?? V(d, s),
338
+ c?.(d, p)
325
339
  ]
326
- }), /* @__PURE__ */ w("div", {
340
+ }), /* @__PURE__ */ E("div", {
327
341
  className: "spotlab-hm-slider-track-row",
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))
342
+ children: [/* @__PURE__ */ v(K, {
343
+ value: n[p],
344
+ color: d.color,
345
+ passive: s,
346
+ onChange: (h) => f(p, h)
347
+ }), /* @__PURE__ */ v(l, {
348
+ value: n[p],
349
+ "aria-label": `${d.label} weight`,
350
+ onChange: (h) => f(p, parseInt(h.target.value || "0", 10))
337
351
  })]
338
352
  })]
339
- }, u.id);
353
+ }, d.id);
340
354
  })
341
355
  });
342
356
  };
343
357
  export {
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
358
+ it as ActionWeightSliders,
359
+ ot as RangeMatrix,
360
+ st as RangeMatrixEditor,
361
+ rt as cellsToComboWeights,
362
+ at as cellsToRanges,
363
+ nt as comboWeightsToCells,
364
+ lt as rangeToMatrix,
365
+ X as rebalance,
366
+ et as seedCells,
367
+ Q as useRangePainting
356
368
  };
@@ -0,0 +1,10 @@
1
+ import { FC } from 'react';
2
+ import { MatrixDisplayProps } from './hand-matrix';
3
+ export type RangeMatrixProps = MatrixDisplayProps;
4
+ /**
5
+ * A read-only 13x13 grid of several actions at once: each cell stacks its
6
+ * actions' weights as colored bands in `order`, hatches dead cells (`avail`
7
+ * 0), and marks capped cells (`avail` below 1) with a cap line and hatched
8
+ * remainder. Draws exactly what `RangeMatrixEditor` does, without painting.
9
+ */
10
+ export declare const RangeMatrix: FC<RangeMatrixProps>;
@@ -0,0 +1,19 @@
1
+ import { HandFrequency } from '@spotlab/range';
2
+ import { RangeMatrixProps } from './range-matrix';
3
+ /**
4
+ * `RangeMatrix`'s props for drawing one range: every cell is fully available
5
+ * and filled to its combo's mean weight in `color` (default
6
+ * `var(--spotlab-hm-fill)`), with the rest left blank. The two actions are
7
+ * `in` and `out`, labeled `labels.in` (default "In range") and `labels.out`
8
+ * (default "Out of range") for a legend of your own. Takes a range string or
9
+ * its expanded hand-keyed form; a string is parsed via `@spotlab/range`, so
10
+ * `initRange()` must have resolved. Parsing and collapsing are wasm calls, so
11
+ * memoize the result rather than calling this on every render.
12
+ */
13
+ export declare function rangeToMatrix(range: HandFrequency[] | string, { color, labels, }?: {
14
+ color?: string;
15
+ labels?: {
16
+ in?: string;
17
+ out?: string;
18
+ };
19
+ }): Omit<RangeMatrixProps, "className">;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spotlab/hand-matrix",
3
- "version": "0.3.0",
3
+ "version": "0.5.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",
@@ -1,13 +0,0 @@
1
- import { FC } from 'react';
2
- import { HandFrequency } from '@spotlab/range';
3
- /**
4
- * A read-only 13x13 range grid: each cell fills to its combo's mean weight.
5
- * Accepts the expanded hand-keyed form or a range string directly — a range
6
- * string is parsed via `@spotlab/range`, so the caller never needs to touch
7
- * the parser itself to render one.
8
- */
9
- export declare const RangeGrid: FC<{
10
- range: HandFrequency[] | string;
11
- fill?: string;
12
- className?: string;
13
- }>;