@spotlab/hand-matrix 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +109 -2
- package/dist/editor/action-sliders.d.ts +35 -0
- package/dist/editor/cells.d.ts +26 -0
- package/dist/editor/matrix-editor.d.ts +24 -0
- package/dist/editor/use-range-painting.d.ts +19 -0
- package/dist/editor/weights.d.ts +13 -0
- package/dist/hand-matrix.css +147 -0
- package/dist/hand-matrix.d.ts +13 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +307 -0
- package/dist/range-grid.d.ts +13 -0
- package/package.json +75 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James DiGioia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,110 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @spotlab/hand-matrix
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A 13x13 poker hand-matrix React component. Peer-deps `react`, `react-dom`,
|
|
4
|
+
and [`@spotlab/range`](../range) — no Tailwind, no bundler-plugin
|
|
5
|
+
requirement.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { RangeGrid } from "@spotlab/hand-matrix";
|
|
9
|
+
import "@spotlab/hand-matrix/style.css";
|
|
10
|
+
import { initRange } from "@spotlab/range";
|
|
11
|
+
|
|
12
|
+
await initRange(); // once, at startup
|
|
13
|
+
|
|
14
|
+
<RangeGrid range="AKs:0.5,QQ" />;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Exports
|
|
18
|
+
|
|
19
|
+
- `HandMatrix` — the bare 13x13 grid primitive. Takes no range data; styling
|
|
20
|
+
is entirely the caller's job via `comboStyle`, keyed by combo string (e.g.
|
|
21
|
+
`"AKs"`).
|
|
22
|
+
- `RangeGrid` — a read-only display component. Accepts the expanded
|
|
23
|
+
`HandFrequency[]` form or a range string directly (parsed via
|
|
24
|
+
`@spotlab/range`), and fills each cell to its combo's mean weight.
|
|
25
|
+
- `cellFillGradient` / `CELL_FILL_DIRECTION` — the canonical left-to-right
|
|
26
|
+
gradient helper used to render a cell's fill.
|
|
27
|
+
|
|
28
|
+
`RangeGrid`'s combo collapse (`toComboWeights`) is a wasm call, so
|
|
29
|
+
`initRange()` must have resolved before rendering it — see
|
|
30
|
+
[`@spotlab/range`](../range)'s own README for why that call is explicit
|
|
31
|
+
rather than automatic.
|
|
32
|
+
|
|
33
|
+
## Theming
|
|
34
|
+
|
|
35
|
+
The package ships `hand-matrix.css` (`@spotlab/hand-matrix/style.css`),
|
|
36
|
+
scoped entirely under a `.spotlab-hm-root` wrapper that every top-level
|
|
37
|
+
component renders for itself, merged with any `className` passed in. Every
|
|
38
|
+
custom property has a default declared on `.spotlab-hm-root`, so the
|
|
39
|
+
component renders correctly with zero theming.
|
|
40
|
+
|
|
41
|
+
Because the `className` you pass to a component lands on that same root, a
|
|
42
|
+
class that sets variables works there too (`<RangeGrid className="my-theme" />`
|
|
43
|
+
with `.my-theme { --spotlab-hm-border: … }`), in addition to the
|
|
44
|
+
`.spotlab-hm-root` selector form below.
|
|
45
|
+
|
|
46
|
+
**To override a variable, target `.spotlab-hm-root` itself** — not `:root`
|
|
47
|
+
and not any other ancestor. Because the package's own default is declared
|
|
48
|
+
directly on `.spotlab-hm-root`, a value set only on an ancestor loses to it
|
|
49
|
+
regardless of specificity (an element's own declared value always beats an
|
|
50
|
+
inherited one). This is the single most likely integration mistake:
|
|
51
|
+
|
|
52
|
+
```css
|
|
53
|
+
/* This does NOT win: */
|
|
54
|
+
:root {
|
|
55
|
+
--spotlab-hm-fill: #f59e0b;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* This does: */
|
|
59
|
+
.spotlab-hm-root {
|
|
60
|
+
--spotlab-hm-fill: #f59e0b;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**The package's defaults live in a named CSS cascade layer** (`@layer
|
|
65
|
+
spotlab-hand-matrix`), not unlayered — this is what makes the above override
|
|
66
|
+
reliable. Per the CSS cascade, unlayered styles always beat every `@layer`
|
|
67
|
+
regardless of specificity or source order, so if the package's defaults were
|
|
68
|
+
unlayered, a plain override would only win when it happened to load _after_
|
|
69
|
+
the package's stylesheet; load it first, or wrap it in your own `@layer`,
|
|
70
|
+
and the override would silently lose instead. Putting the defaults in a
|
|
71
|
+
named layer makes any unlayered override — the common case for a plain CSS
|
|
72
|
+
file or style tag, which is exactly what most consumers write — win
|
|
73
|
+
automatically, regardless of import order.
|
|
74
|
+
|
|
75
|
+
The one case this doesn't cover for free: if **your own styles are also
|
|
76
|
+
inside a named layer** (for example, a Tailwind v4 app that wraps its design
|
|
77
|
+
tokens in `@layer base`), layer priority is decided by each layer name's
|
|
78
|
+
_first appearance_ in the stylesheet, not by where the override rule itself
|
|
79
|
+
sits. A layer named for the first time later in the cascade outranks one
|
|
80
|
+
named earlier, so you must import `@spotlab/hand-matrix/style.css` **before**
|
|
81
|
+
whatever first establishes your own layer order (e.g. before `@import
|
|
82
|
+
"tailwindcss"`, which emits `@layer theme, base, components, utilities;` as
|
|
83
|
+
its first statement) — otherwise `spotlab-hand-matrix` gets appended last and
|
|
84
|
+
outranks your override instead:
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
/* Correct: registers `spotlab-hand-matrix` before Tailwind's layers, so
|
|
88
|
+
`@layer base` (where your override lives) outranks it. */
|
|
89
|
+
@import "@spotlab/hand-matrix/style.css";
|
|
90
|
+
@import "tailwindcss";
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If you don't use cascade layers yourself (e.g. a plain CSS file, or an
|
|
94
|
+
Obsidian plugin with no bundler-level layer system), none of this applies —
|
|
95
|
+
your override is unlayered and wins unconditionally, in any import order.
|
|
96
|
+
|
|
97
|
+
Available variables: `--spotlab-hm-border`, `--spotlab-hm-border-width`,
|
|
98
|
+
`--spotlab-hm-cell-bg`, `--spotlab-hm-cell-fg`, `--spotlab-hm-fill`,
|
|
99
|
+
`--spotlab-hm-font-family`, `--spotlab-hm-font-size`,
|
|
100
|
+
`--spotlab-hm-font-weight`, `--spotlab-hm-radius`, `--spotlab-hm-surface-bg`,
|
|
101
|
+
`--spotlab-hm-muted-bg`, `--spotlab-hm-muted-fg`, `--spotlab-hm-track-bg`,
|
|
102
|
+
`--spotlab-hm-row-passive-bg`, `--spotlab-hm-dead-hatch`.
|
|
103
|
+
|
|
104
|
+
Action/fill colors (`RangeGrid`'s `fill` prop) are **not** theme variables —
|
|
105
|
+
they are data, passed as props. `--spotlab-hm-fill` is only the default used
|
|
106
|
+
when `fill` is omitted.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ChangeEvent, ComponentType, FC, ReactNode } from 'react';
|
|
2
|
+
import { EditorActionSpec } from './weights';
|
|
3
|
+
export type WeightInputProps = {
|
|
4
|
+
value: number;
|
|
5
|
+
"aria-label": string;
|
|
6
|
+
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
7
|
+
};
|
|
8
|
+
export type ActionWeightSlidersProps = {
|
|
9
|
+
actions: EditorActionSpec[];
|
|
10
|
+
/** Indices into `actions`/`weights`, in the order rows should render. */
|
|
11
|
+
order: number[];
|
|
12
|
+
/** Index-aligned with `actions`. */
|
|
13
|
+
weights: number[];
|
|
14
|
+
passive: number;
|
|
15
|
+
onWeightsChange: (weights: number[]) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Leading content for a row's header, replacing the default label — the
|
|
18
|
+
* slot for a control that takes the label's own position (e.g. a
|
|
19
|
+
* raise-size input) rather than trailing it. Defaults to the action's
|
|
20
|
+
* label (plus a "· remainder" suffix on the passive row).
|
|
21
|
+
*/
|
|
22
|
+
renderActionLabel?: (action: EditorActionSpec, index: number, isPassive: boolean) => ReactNode;
|
|
23
|
+
/** Extra content rendered after an action's label — the slot for consumer-specific controls (a remove button). */
|
|
24
|
+
renderActionExtra?: (action: EditorActionSpec, index: number) => ReactNode;
|
|
25
|
+
components?: {
|
|
26
|
+
WeightInput?: ComponentType<WeightInputProps>;
|
|
27
|
+
};
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* One pointer-driven weight slider per action, each paired with a numeric
|
|
32
|
+
* input for keyboard entry. Adjusting one action rebalances the others
|
|
33
|
+
* proportionally so the row's weights keep summing to 100.
|
|
34
|
+
*/
|
|
35
|
+
export declare const ActionWeightSliders: FC<ActionWeightSlidersProps>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const COMBO_INDEX: Map<string, number>;
|
|
2
|
+
/**
|
|
3
|
+
* One matrix cell's painted state: `avail` is the combo's cap (its weight in the
|
|
4
|
+
* parent range the editor is splitting), `weights` are each action's conditional
|
|
5
|
+
* share of that cap, summing to 100 across the node's actions.
|
|
6
|
+
*/
|
|
7
|
+
export type EditorCell = {
|
|
8
|
+
avail: number;
|
|
9
|
+
weights: number[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Seed the 169-cell grid from a parent range's per-combo availability and each
|
|
13
|
+
* action's own per-combo range, both expressed as absolute weights (a share of
|
|
14
|
+
* the whole deck, as `toComboWeights` from `@spotlab/range` produces). `avail`
|
|
15
|
+
* of `null` means the combo is unconstrained (available at 1). `passive` must be
|
|
16
|
+
* a valid index into `actionWeights` naming the remainder action.
|
|
17
|
+
*/
|
|
18
|
+
export declare function seedCells(avail: Map<string, number> | null, actionWeights: (Map<string, number> | null)[], passive: number): EditorCell[];
|
|
19
|
+
/** Collapse one action's conditional weights back to absolute per-combo weights. */
|
|
20
|
+
export declare function cellsToComboWeights(cells: EditorCell[], actionIndex: number): Map<string, number>;
|
|
21
|
+
/**
|
|
22
|
+
* Re-derive conditional cell weights from absolute per-action ranges — the
|
|
23
|
+
* inverse of `cellsToComboWeights`. `passive` must be a valid index into
|
|
24
|
+
* `actionWeights` naming the remainder action.
|
|
25
|
+
*/
|
|
26
|
+
export declare function comboWeightsToCells(cells: EditorCell[], actionWeights: (Map<string, number> | null)[], passive: number): EditorCell[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { PaintTool } from './use-range-painting';
|
|
3
|
+
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[];
|
|
12
|
+
brush: number[];
|
|
13
|
+
tool: PaintTool;
|
|
14
|
+
onCellsChange: (cells: EditorCell[]) => void;
|
|
15
|
+
onBrushChange: (brush: number[]) => void;
|
|
16
|
+
onToolChange: (tool: PaintTool) => void;
|
|
17
|
+
className?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
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.
|
|
23
|
+
*/
|
|
24
|
+
export declare const RangeMatrixEditor: FC<RangeMatrixEditorProps>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EditorCell } from './cells';
|
|
2
|
+
export type PaintTool = "brush" | "eyedropper";
|
|
3
|
+
/**
|
|
4
|
+
* The brush/eyedropper state machine behind `RangeMatrixEditor`: drag-painting
|
|
5
|
+
* (tracked across `pointerdown`/mouse-enter without a React re-render per
|
|
6
|
+
* cell) and eyedropping a cell's weights back onto the brush. Fully
|
|
7
|
+
* controlled — all state changes go out through the `on*Change` callbacks.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useRangePainting(opts: {
|
|
10
|
+
cells: EditorCell[];
|
|
11
|
+
brush: number[];
|
|
12
|
+
tool: PaintTool;
|
|
13
|
+
onCellsChange: (cells: EditorCell[]) => void;
|
|
14
|
+
onBrushChange: (brush: number[]) => void;
|
|
15
|
+
onToolChange: (tool: PaintTool) => void;
|
|
16
|
+
}): {
|
|
17
|
+
onCellDown: (combo: string) => void;
|
|
18
|
+
onCellEnter: (combo: string) => void;
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const fixRound: (weights: number[], keep: number) => number[];
|
|
2
|
+
/**
|
|
3
|
+
* `passive` names the remainder action — the one whose weight absorbs whatever
|
|
4
|
+
* the others leave behind. It must be a valid index into `weights`; the caller
|
|
5
|
+
* decides which action that is and guarantees one exists.
|
|
6
|
+
*/
|
|
7
|
+
export declare const rebalance: (weights: number[], index: number, value: number, passive: number) => number[];
|
|
8
|
+
export declare const sumActive: (weights: number[], passive: number) => number;
|
|
9
|
+
export type EditorActionSpec = {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
color: string;
|
|
13
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Defaults live in a named layer, not unlayered: unlayered CSS always beats
|
|
3
|
+
* every `@layer`, even one a consumer wrote specifically to override these
|
|
4
|
+
* variables. A named layer lets a consumer's plain (unlayered) override CSS
|
|
5
|
+
* win regardless of import order, and lets a consumer who uses cascade
|
|
6
|
+
* layers themselves (e.g. Tailwind) place their own override layer after
|
|
7
|
+
* this one. See the "Theming" section of README.md for how to register this
|
|
8
|
+
* layer's priority relative to a bundler-level layer system.
|
|
9
|
+
*/
|
|
10
|
+
@layer spotlab-hand-matrix {
|
|
11
|
+
.spotlab-hm-root {
|
|
12
|
+
--spotlab-hm-border: #d4d4d8;
|
|
13
|
+
--spotlab-hm-border-width: 1px;
|
|
14
|
+
--spotlab-hm-cell-bg: transparent;
|
|
15
|
+
--spotlab-hm-cell-fg: #18181b;
|
|
16
|
+
--spotlab-hm-muted-bg: #f4f4f5;
|
|
17
|
+
--spotlab-hm-muted-fg: #71717a;
|
|
18
|
+
--spotlab-hm-fill: #10b981;
|
|
19
|
+
--spotlab-hm-font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
20
|
+
--spotlab-hm-font-size: 0.75rem;
|
|
21
|
+
--spotlab-hm-font-weight: 600;
|
|
22
|
+
--spotlab-hm-dead-hatch: repeating-linear-gradient(
|
|
23
|
+
45deg,
|
|
24
|
+
rgba(15, 23, 42, 0.22) 0 3px,
|
|
25
|
+
rgba(255, 255, 255, 0.18) 3px 6px
|
|
26
|
+
);
|
|
27
|
+
--spotlab-hm-radius: 0.5rem;
|
|
28
|
+
--spotlab-hm-surface-bg: #ffffff;
|
|
29
|
+
--spotlab-hm-track-bg: #e4e4e7;
|
|
30
|
+
--spotlab-hm-row-passive-bg: rgba(244, 244, 245, 0.4);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.spotlab-hm-row {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-flow: row nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.spotlab-hm-cell {
|
|
40
|
+
aspect-ratio: 1 / 1;
|
|
41
|
+
width: calc(100% / 13);
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
background-color: var(--spotlab-hm-cell-bg);
|
|
44
|
+
border: var(--spotlab-hm-border-width) solid var(--spotlab-hm-border);
|
|
45
|
+
font-family: var(--spotlab-hm-font-family);
|
|
46
|
+
font-size: var(--spotlab-hm-font-size);
|
|
47
|
+
font-weight: var(--spotlab-hm-font-weight);
|
|
48
|
+
color: var(--spotlab-hm-cell-fg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.spotlab-hm-cell-inner {
|
|
52
|
+
display: flex;
|
|
53
|
+
height: 100%;
|
|
54
|
+
width: 100%;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.spotlab-hm-sliders {
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
gap: 8px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.spotlab-hm-slider-row {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
border-radius: var(--spotlab-hm-radius);
|
|
70
|
+
border: var(--spotlab-hm-border-width) solid var(--spotlab-hm-border);
|
|
71
|
+
padding: 10px 12px;
|
|
72
|
+
background: var(--spotlab-hm-surface-bg);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.spotlab-hm-slider-row[data-passive="true"] {
|
|
76
|
+
background: var(--spotlab-hm-row-passive-bg);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.spotlab-hm-slider-header {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 8px;
|
|
83
|
+
margin-bottom: 8px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.spotlab-hm-slider-swatch {
|
|
87
|
+
flex: none;
|
|
88
|
+
height: 14px;
|
|
89
|
+
width: 14px;
|
|
90
|
+
border-radius: 3px;
|
|
91
|
+
border: var(--spotlab-hm-border-width) solid var(--spotlab-hm-border);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.spotlab-hm-slider-label {
|
|
95
|
+
flex: 1;
|
|
96
|
+
font-size: 13px;
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
font-family: var(--spotlab-hm-font-family);
|
|
99
|
+
color: var(--spotlab-hm-cell-fg);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.spotlab-hm-slider-remainder {
|
|
103
|
+
font-size: 12px;
|
|
104
|
+
font-weight: 400;
|
|
105
|
+
color: var(--spotlab-hm-muted-fg);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.spotlab-hm-slider-track-row {
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
gap: 10px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.spotlab-hm-slider-track {
|
|
115
|
+
position: relative;
|
|
116
|
+
flex: 1;
|
|
117
|
+
height: 8px;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
border-radius: 5px;
|
|
120
|
+
border: var(--spotlab-hm-border-width) solid var(--spotlab-hm-border);
|
|
121
|
+
touch-action: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.spotlab-hm-slider-thumb {
|
|
125
|
+
position: absolute;
|
|
126
|
+
top: 50%;
|
|
127
|
+
height: 16px;
|
|
128
|
+
width: 16px;
|
|
129
|
+
transform: translate(-50%, -50%);
|
|
130
|
+
border-radius: 50%;
|
|
131
|
+
background: var(--spotlab-hm-surface-bg);
|
|
132
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.spotlab-hm-weight-input {
|
|
136
|
+
height: 32px;
|
|
137
|
+
width: 58px;
|
|
138
|
+
flex: none;
|
|
139
|
+
padding: 0 6px;
|
|
140
|
+
text-align: center;
|
|
141
|
+
font-size: 13px;
|
|
142
|
+
font-family: var(--spotlab-hm-font-family);
|
|
143
|
+
color: var(--spotlab-hm-cell-fg);
|
|
144
|
+
background: transparent;
|
|
145
|
+
border-radius: calc(var(--spotlab-hm-radius) * 0.5);
|
|
146
|
+
border: var(--spotlab-hm-border-width) solid var(--spotlab-hm-border);
|
|
147
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CSSProperties, FC } from 'react';
|
|
2
|
+
export declare const CELL_FILL_DIRECTION = "to right";
|
|
3
|
+
export declare const cellFillGradient: (stops: string[]) => string;
|
|
4
|
+
type ComboHandlers = {
|
|
5
|
+
onClick?: (combo: string) => void;
|
|
6
|
+
onMouseDown?: (combo: string) => void;
|
|
7
|
+
onMouseEnter?: (combo: string) => void;
|
|
8
|
+
comboStyle?: (combo: string) => CSSProperties;
|
|
9
|
+
};
|
|
10
|
+
export declare const HandMatrix: FC<{
|
|
11
|
+
className?: string;
|
|
12
|
+
} & ComboHandlers>;
|
|
13
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { HandMatrix, cellFillGradient, CELL_FILL_DIRECTION, } from './hand-matrix';
|
|
2
|
+
export { RangeGrid } from './range-grid';
|
|
3
|
+
export { RangeMatrixEditor } from './editor/matrix-editor';
|
|
4
|
+
export type { RangeMatrixEditorProps } from './editor/matrix-editor';
|
|
5
|
+
export { ActionWeightSliders } from './editor/action-sliders';
|
|
6
|
+
export type { ActionWeightSlidersProps, WeightInputProps, } from './editor/action-sliders';
|
|
7
|
+
export { useRangePainting } from './editor/use-range-painting';
|
|
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';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { COMBOS as g, parseRange as R, toComboWeights as x } from "@spotlab/range";
|
|
2
|
+
import { jsx as p, jsxs as b } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback as v, useEffect as k, useMemo as L, useRef as w } from "react";
|
|
4
|
+
var I = (e, t) => Array.from({ length: Math.ceil(e.length / t) }, (n, r) => e.slice(r * t, r * t + t)), D = "to right", $ = (e) => e.length === 0 ? "transparent" : `linear-gradient(${D}, ${e.join(", ")})`, B = ({ combo: e, onClick: t, onMouseDown: n, onMouseEnter: r, comboStyle: a }) => /* @__PURE__ */ p("div", {
|
|
5
|
+
className: "spotlab-hm-cell",
|
|
6
|
+
style: a?.(e),
|
|
7
|
+
onClick: () => t?.(e),
|
|
8
|
+
onMouseDown: () => n?.(e),
|
|
9
|
+
onMouseEnter: () => r?.(e),
|
|
10
|
+
children: /* @__PURE__ */ p("div", {
|
|
11
|
+
className: "spotlab-hm-cell-inner",
|
|
12
|
+
children: /* @__PURE__ */ p("div", {
|
|
13
|
+
"data-combo": e,
|
|
14
|
+
children: e
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
}), O = ({ row: e, ...t }) => /* @__PURE__ */ p("div", {
|
|
18
|
+
className: "spotlab-hm-row",
|
|
19
|
+
children: e.map((n, r) => /* @__PURE__ */ p(B, {
|
|
20
|
+
combo: n,
|
|
21
|
+
...t
|
|
22
|
+
}, r))
|
|
23
|
+
}), N = ({ className: e, ...t }) => /* @__PURE__ */ p("div", {
|
|
24
|
+
className: ["spotlab-hm-root", e].filter(Boolean).join(" "),
|
|
25
|
+
children: I(g, 13).map((n, r) => /* @__PURE__ */ p(O, {
|
|
26
|
+
row: n,
|
|
27
|
+
...t
|
|
28
|
+
}, r))
|
|
29
|
+
}), S = (e, t) => ({ background: $([`${e} 0 ${t * 100}%`, `transparent ${t * 100}% 100%`]) }), H = ({ range: e, fill: t = "var(--spotlab-hm-fill)", className: n }) => {
|
|
30
|
+
const r = L(() => {
|
|
31
|
+
const a = typeof e == "string" ? R(e) : e;
|
|
32
|
+
return x(a);
|
|
33
|
+
}, [e]);
|
|
34
|
+
return /* @__PURE__ */ p(N, {
|
|
35
|
+
className: n,
|
|
36
|
+
comboStyle: (a) => {
|
|
37
|
+
const o = r.get(a);
|
|
38
|
+
return o ? S(t, o) : {};
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}, W = (e) => Math.max(0, Math.min(100, Math.round(e))), F = (e, t) => {
|
|
42
|
+
const n = e.map(Math.round);
|
|
43
|
+
let r = 100 - n.reduce((a, o) => a + o, 0);
|
|
44
|
+
if (r !== 0) {
|
|
45
|
+
for (let a = 0; a < n.length; a++) if (a !== t && n[a] + r >= 0) {
|
|
46
|
+
n[a] += r, r = 0;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (r !== 0) {
|
|
50
|
+
for (let a = 0; a < n.length; a++) if (a !== t && n[a] > 0) {
|
|
51
|
+
const o = Math.min(n[a], -r);
|
|
52
|
+
if (n[a] -= o, r += o, r === 0) break;
|
|
53
|
+
}
|
|
54
|
+
n[t] = Math.max(0, n[t] + r);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return n;
|
|
58
|
+
}, j = (e, t, n, r) => {
|
|
59
|
+
const a = W(n), o = e.slice();
|
|
60
|
+
o[t] = a;
|
|
61
|
+
const i = 100 - a, l = [];
|
|
62
|
+
for (let s = 0; s < e.length; s++) s !== t && l.push(s);
|
|
63
|
+
const c = l.reduce((s, u) => s + e[u], 0);
|
|
64
|
+
if (c <= 0) {
|
|
65
|
+
l.forEach((u) => o[u] = 0);
|
|
66
|
+
const s = t === r ? l[0] : r;
|
|
67
|
+
s != null && (o[s] = i);
|
|
68
|
+
} else l.forEach((s) => o[s] = Math.round(e[s] / c * i));
|
|
69
|
+
return F(o, t);
|
|
70
|
+
}, M = (e, t) => e.reduce((n, r, a) => a === t ? n : n + r, 0), C = new Map(g.map((e, t) => [e, t])), y = (e, t) => {
|
|
71
|
+
const n = e.slice();
|
|
72
|
+
let r = M(n, t);
|
|
73
|
+
if (r > 100) {
|
|
74
|
+
const a = 100 / r;
|
|
75
|
+
for (let o = 0; o < n.length; o++) o !== t && (n[o] = Math.round(n[o] * a));
|
|
76
|
+
r = M(n, t);
|
|
77
|
+
}
|
|
78
|
+
return n[t] = Math.max(0, 100 - r), n;
|
|
79
|
+
};
|
|
80
|
+
function Q(e, t, n) {
|
|
81
|
+
return g.map((r) => {
|
|
82
|
+
const a = e ? e.get(r) ?? 0 : 1, o = t.map(() => 0);
|
|
83
|
+
return a <= 0 ? (o[n] = 100, {
|
|
84
|
+
avail: 0,
|
|
85
|
+
weights: o
|
|
86
|
+
}) : (t.forEach((i, l) => {
|
|
87
|
+
const c = i?.get(r) ?? 0;
|
|
88
|
+
o[l] = Math.round(c / a * 100);
|
|
89
|
+
}), {
|
|
90
|
+
avail: a,
|
|
91
|
+
weights: y(o, n)
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
var T = 8;
|
|
96
|
+
function Y(e, t) {
|
|
97
|
+
const n = /* @__PURE__ */ new Map();
|
|
98
|
+
return e.forEach((r, a) => {
|
|
99
|
+
if (r.avail <= 0) return;
|
|
100
|
+
const o = r.weights.reduce((l, c) => l + c, 0);
|
|
101
|
+
if (!o) return;
|
|
102
|
+
const i = Number((r.weights[t] / o * r.avail).toFixed(T));
|
|
103
|
+
i && n.set(g[a], i);
|
|
104
|
+
}), n;
|
|
105
|
+
}
|
|
106
|
+
function J(e, t, n) {
|
|
107
|
+
return e.map((r, a) => {
|
|
108
|
+
const o = t.map(() => 0);
|
|
109
|
+
if (r.avail <= 0)
|
|
110
|
+
return o[n] = 100, {
|
|
111
|
+
avail: r.avail,
|
|
112
|
+
weights: o
|
|
113
|
+
};
|
|
114
|
+
const i = g[a];
|
|
115
|
+
return t.forEach((l, c) => {
|
|
116
|
+
const s = l?.get(i) ?? 0;
|
|
117
|
+
o[c] = Math.round(s / r.avail * 100);
|
|
118
|
+
}), {
|
|
119
|
+
avail: r.avail,
|
|
120
|
+
weights: y(o, n)
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function _(e) {
|
|
125
|
+
const { cells: t, brush: n, tool: r, onCellsChange: a, onBrushChange: o, onToolChange: i } = e, l = w(!1);
|
|
126
|
+
k(() => {
|
|
127
|
+
const u = () => l.current = !1;
|
|
128
|
+
return window.addEventListener("pointerup", u), () => window.removeEventListener("pointerup", u);
|
|
129
|
+
}, []);
|
|
130
|
+
const c = v((u) => {
|
|
131
|
+
const d = C.get(u);
|
|
132
|
+
if (t[d].avail <= 0) return;
|
|
133
|
+
const h = t.slice();
|
|
134
|
+
h[d] = {
|
|
135
|
+
avail: t[d].avail,
|
|
136
|
+
weights: [...n]
|
|
137
|
+
}, a(h);
|
|
138
|
+
}, [
|
|
139
|
+
t,
|
|
140
|
+
n,
|
|
141
|
+
a
|
|
142
|
+
]), s = v((u) => {
|
|
143
|
+
const d = C.get(u);
|
|
144
|
+
t[d].avail <= 0 || (o([...t[d].weights]), i("brush"));
|
|
145
|
+
}, [
|
|
146
|
+
t,
|
|
147
|
+
o,
|
|
148
|
+
i
|
|
149
|
+
]);
|
|
150
|
+
return {
|
|
151
|
+
onCellDown: v((u) => {
|
|
152
|
+
if (r === "eyedropper") {
|
|
153
|
+
s(u);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
l.current = !0, c(u);
|
|
157
|
+
}, [
|
|
158
|
+
r,
|
|
159
|
+
s,
|
|
160
|
+
c
|
|
161
|
+
]),
|
|
162
|
+
onCellEnter: v((u) => {
|
|
163
|
+
l.current && r === "brush" && c(u);
|
|
164
|
+
}, [r, c])
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
var E = (e, t, n) => {
|
|
168
|
+
const r = [];
|
|
169
|
+
let a = 0;
|
|
170
|
+
return n.forEach((o) => {
|
|
171
|
+
const i = (e.weights[o] || 0) / 100 * e.avail;
|
|
172
|
+
if (i <= 0) return;
|
|
173
|
+
const l = t[o].color;
|
|
174
|
+
r.push(`${l} ${a * 100}%`), r.push(`${l} ${(a + i) * 100}%`), a += i;
|
|
175
|
+
}), $(r);
|
|
176
|
+
}, K = ({ cells: e, actions: t, order: n, brush: r, tool: a, onCellsChange: o, onBrushChange: i, onToolChange: l, className: c }) => {
|
|
177
|
+
const { onCellDown: s, onCellEnter: u } = _({
|
|
178
|
+
cells: e,
|
|
179
|
+
brush: r,
|
|
180
|
+
tool: a,
|
|
181
|
+
onCellsChange: o,
|
|
182
|
+
onBrushChange: i,
|
|
183
|
+
onToolChange: l
|
|
184
|
+
});
|
|
185
|
+
return /* @__PURE__ */ p(N, {
|
|
186
|
+
className: c,
|
|
187
|
+
comboStyle: (d) => {
|
|
188
|
+
const h = e[C.get(d)];
|
|
189
|
+
if (h.avail <= 0) return {
|
|
190
|
+
background: "var(--spotlab-hm-dead-hatch)",
|
|
191
|
+
backgroundColor: "var(--spotlab-hm-muted-bg)",
|
|
192
|
+
cursor: "not-allowed",
|
|
193
|
+
color: "var(--spotlab-hm-muted-fg)"
|
|
194
|
+
};
|
|
195
|
+
const m = a === "eyedropper" ? "copy" : "pointer";
|
|
196
|
+
if (h.avail < 1) {
|
|
197
|
+
const f = `${h.avail * 100}%`;
|
|
198
|
+
return {
|
|
199
|
+
background: `repeating-linear-gradient(180deg, var(--spotlab-hm-muted-fg) 0 4px, transparent 4px 8px), var(--spotlab-hm-dead-hatch), ${E(h, t, n)}`,
|
|
200
|
+
backgroundSize: `1px 100%, ${(1 - h.avail) * 100}% 100%, ${h.avail * 100}% 100%`,
|
|
201
|
+
backgroundPosition: `${f} center, right center, left center`,
|
|
202
|
+
backgroundRepeat: "no-repeat, no-repeat, no-repeat",
|
|
203
|
+
cursor: m
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
background: E(h, t, n),
|
|
208
|
+
cursor: m
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
onMouseDown: s,
|
|
212
|
+
onMouseEnter: u
|
|
213
|
+
});
|
|
214
|
+
}, A = (e) => /* @__PURE__ */ p("input", {
|
|
215
|
+
type: "number",
|
|
216
|
+
className: "spotlab-hm-weight-input",
|
|
217
|
+
...e
|
|
218
|
+
}), P = ({ value: e, color: t, passive: n, onChange: r }) => {
|
|
219
|
+
const a = w(null), o = w(!1), i = v((l) => {
|
|
220
|
+
const c = a.current;
|
|
221
|
+
if (!c) return;
|
|
222
|
+
const s = c.getBoundingClientRect();
|
|
223
|
+
r((l - s.left) / s.width * 100);
|
|
224
|
+
}, [r]);
|
|
225
|
+
return k(() => {
|
|
226
|
+
const l = (s) => {
|
|
227
|
+
o.current && i(s.clientX);
|
|
228
|
+
}, c = () => o.current = !1;
|
|
229
|
+
return window.addEventListener("pointermove", l), window.addEventListener("pointerup", c), () => {
|
|
230
|
+
window.removeEventListener("pointermove", l), window.removeEventListener("pointerup", c);
|
|
231
|
+
};
|
|
232
|
+
}, [i]), /* @__PURE__ */ p("div", {
|
|
233
|
+
ref: a,
|
|
234
|
+
role: "slider",
|
|
235
|
+
"aria-valuenow": e,
|
|
236
|
+
"aria-valuemin": 0,
|
|
237
|
+
"aria-valuemax": 100,
|
|
238
|
+
className: "spotlab-hm-slider-track",
|
|
239
|
+
style: { background: `linear-gradient(to right, ${t} 0 ${e}%, var(--spotlab-hm-track-bg) ${e}% 100%)` },
|
|
240
|
+
onPointerDown: (l) => {
|
|
241
|
+
o.current = !0, i(l.clientX);
|
|
242
|
+
},
|
|
243
|
+
children: /* @__PURE__ */ p("div", {
|
|
244
|
+
className: "spotlab-hm-slider-thumb",
|
|
245
|
+
style: {
|
|
246
|
+
left: `${e}%`,
|
|
247
|
+
border: `2px solid ${n ? "var(--spotlab-hm-muted-fg)" : t}`
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
});
|
|
251
|
+
}, X = (e, t) => /* @__PURE__ */ b("span", {
|
|
252
|
+
className: "spotlab-hm-slider-label",
|
|
253
|
+
children: [e.label, t && /* @__PURE__ */ p("span", {
|
|
254
|
+
className: "spotlab-hm-slider-remainder",
|
|
255
|
+
children: " · remainder"
|
|
256
|
+
})]
|
|
257
|
+
}), V = ({ actions: e, order: t, weights: n, passive: r, onWeightsChange: a, renderActionLabel: o, renderActionExtra: i, components: l, className: c }) => {
|
|
258
|
+
const s = l?.WeightInput ?? A, u = (d, h) => {
|
|
259
|
+
a(j(n, d, h, r));
|
|
260
|
+
};
|
|
261
|
+
return /* @__PURE__ */ p("div", {
|
|
262
|
+
className: ["spotlab-hm-root spotlab-hm-sliders", c].filter(Boolean).join(" "),
|
|
263
|
+
children: t.map((d) => {
|
|
264
|
+
const h = e[d], m = d === r;
|
|
265
|
+
return /* @__PURE__ */ b("div", {
|
|
266
|
+
className: "spotlab-hm-slider-row",
|
|
267
|
+
"data-passive": m,
|
|
268
|
+
children: [/* @__PURE__ */ b("div", {
|
|
269
|
+
className: "spotlab-hm-slider-header",
|
|
270
|
+
children: [
|
|
271
|
+
/* @__PURE__ */ p("span", {
|
|
272
|
+
className: "spotlab-hm-slider-swatch",
|
|
273
|
+
style: { background: h.color }
|
|
274
|
+
}),
|
|
275
|
+
o?.(h, d, m) ?? X(h, m),
|
|
276
|
+
i?.(h, d)
|
|
277
|
+
]
|
|
278
|
+
}), /* @__PURE__ */ b("div", {
|
|
279
|
+
className: "spotlab-hm-slider-track-row",
|
|
280
|
+
children: [/* @__PURE__ */ p(P, {
|
|
281
|
+
value: n[d],
|
|
282
|
+
color: h.color,
|
|
283
|
+
passive: m,
|
|
284
|
+
onChange: (f) => u(d, f)
|
|
285
|
+
}), /* @__PURE__ */ p(s, {
|
|
286
|
+
value: n[d],
|
|
287
|
+
"aria-label": `${h.label} weight`,
|
|
288
|
+
onChange: (f) => u(d, parseInt(f.target.value || "0", 10))
|
|
289
|
+
})]
|
|
290
|
+
})]
|
|
291
|
+
}, h.id);
|
|
292
|
+
})
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
export {
|
|
296
|
+
V as ActionWeightSliders,
|
|
297
|
+
D as CELL_FILL_DIRECTION,
|
|
298
|
+
N as HandMatrix,
|
|
299
|
+
H as RangeGrid,
|
|
300
|
+
K as RangeMatrixEditor,
|
|
301
|
+
$ as cellFillGradient,
|
|
302
|
+
Y as cellsToComboWeights,
|
|
303
|
+
J as comboWeightsToCells,
|
|
304
|
+
j as rebalance,
|
|
305
|
+
Q as seedCells,
|
|
306
|
+
_ as useRangePainting
|
|
307
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spotlab/hand-matrix",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/mAAdhaTTah/spotlab",
|
|
10
|
+
"directory": "packages/hand-matrix"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/mAAdhaTTah/spotlab/tree/main/packages/hand-matrix#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/mAAdhaTTah/spotlab/issues"
|
|
15
|
+
},
|
|
16
|
+
"author": "James DiGioia",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"poker",
|
|
19
|
+
"range",
|
|
20
|
+
"hand-matrix",
|
|
21
|
+
"react",
|
|
22
|
+
"component",
|
|
23
|
+
"texas-holdem"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": [
|
|
26
|
+
"*.css"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./style.css": "./dist/hand-matrix.css"
|
|
34
|
+
},
|
|
35
|
+
"main": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@spotlab/range": "^0.1.0",
|
|
44
|
+
"react": "^19.0.0",
|
|
45
|
+
"react-dom": "^19.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@spotlab/range": "0.1.0",
|
|
49
|
+
"@storybook/addon-vitest": "^10.5.5",
|
|
50
|
+
"@storybook/react-vite": "^10.5.5",
|
|
51
|
+
"@types/react": "^19.2.16",
|
|
52
|
+
"@types/react-dom": "^19.2.3",
|
|
53
|
+
"@vitejs/plugin-react": "^6.0.0",
|
|
54
|
+
"@vitest/browser": "^4.1.4",
|
|
55
|
+
"@vitest/browser-playwright": "4.1.11",
|
|
56
|
+
"playwright": "^1.49.1",
|
|
57
|
+
"react": "^19.2.7",
|
|
58
|
+
"react-dom": "^19.2.7",
|
|
59
|
+
"storybook": "^10.5.5",
|
|
60
|
+
"typescript": "^6.0.0",
|
|
61
|
+
"vite": "^8.3.1",
|
|
62
|
+
"vite-plugin-dts": "^5.1.2",
|
|
63
|
+
"vitest": "^4.1.4"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
70
|
+
"build:vite": "vite build",
|
|
71
|
+
"build:css": "node scripts/copy-css.mjs",
|
|
72
|
+
"build": "pnpm typecheck && pnpm build:vite && pnpm build:css",
|
|
73
|
+
"test": "vitest run --project unit",
|
|
74
|
+
"storybook": "storybook dev -p 6007",
|
|
75
|
+
"build-storybook": "storybook build",
|
|
76
|
+
"test-storybook": "vitest run --project storybook"
|
|
77
|
+
}
|
|
6
78
|
}
|