@tungstenstudio/dartboard-input 1.0.0-rc.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 +15 -0
- package/README.md +294 -0
- package/dist/index.cjs +450 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +91 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +415 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Tungsten Studio
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# @tungstenstudio/dartboard-input
|
|
2
|
+
|
|
3
|
+
Interactive SVG dartboard input component built with D3. Click, tap, or keyboard-navigate beds to fire throw events.
|
|
4
|
+
|
|
5
|
+
**[Live Demo](https://tungstenstudio.gitlab.io/libs/dartboard-input/)**
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @tungstenstudio/dartboard-input d3-selection d3-shape
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @tungstenstudio/dartboard-input d3-selection d3-shape
|
|
13
|
+
# or
|
|
14
|
+
yarn add @tungstenstudio/dartboard-input d3-selection d3-shape
|
|
15
|
+
# or
|
|
16
|
+
bun add @tungstenstudio/dartboard-input d3-selection d3-shape
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { Dartboard } from '@tungstenstudio/dartboard-input';
|
|
23
|
+
import '@tungstenstudio/dartboard-input/style.css';
|
|
24
|
+
|
|
25
|
+
const board = new Dartboard('#dartboard');
|
|
26
|
+
board.render();
|
|
27
|
+
|
|
28
|
+
document.querySelector('#dartboard').addEventListener('throw', (e) => {
|
|
29
|
+
console.log(e.detail); // { bed: 'T20', ring: 'triple', score: 60 }
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## React Usage
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { useRef, useEffect } from 'react';
|
|
37
|
+
import { Dartboard } from '@tungstenstudio/dartboard-input';
|
|
38
|
+
import '@tungstenstudio/dartboard-input/style.css';
|
|
39
|
+
|
|
40
|
+
function DartboardInput({ onThrow }) {
|
|
41
|
+
const ref = useRef(null);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const board = new Dartboard(ref.current, { size: 400 });
|
|
45
|
+
board.render();
|
|
46
|
+
|
|
47
|
+
const handler = (e) => onThrow?.(e.detail);
|
|
48
|
+
ref.current.addEventListener('throw', handler);
|
|
49
|
+
|
|
50
|
+
return () => board.destroy();
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
return <div ref={ref} />;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### `new Dartboard(container, options?)`
|
|
60
|
+
|
|
61
|
+
Creates a dartboard instance.
|
|
62
|
+
|
|
63
|
+
| Parameter | Type | Description |
|
|
64
|
+
|-----------|------|-------------|
|
|
65
|
+
| `container` | `string \| HTMLElement` | CSS selector or DOM element |
|
|
66
|
+
| `options` | `Partial<DartboardOptions>` | Size and ring proportions |
|
|
67
|
+
|
|
68
|
+
### `board.render(): this`
|
|
69
|
+
|
|
70
|
+
Renders the dartboard SVG. Chainable.
|
|
71
|
+
|
|
72
|
+
### `board.destroy(): void`
|
|
73
|
+
|
|
74
|
+
Removes the board from the DOM and cleans up references.
|
|
75
|
+
|
|
76
|
+
### `board.throwAt(target): void`
|
|
77
|
+
|
|
78
|
+
Programmatically triggers a throw event.
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
board.throwAt('T20'); // bed string
|
|
82
|
+
board.throwAt({ segment: 20, ring: 'TRIPLE' }); // explicit BedTarget
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `board.highlight(targets, options?): void`
|
|
86
|
+
|
|
87
|
+
Adds a CSS class to targeted beds. All targeting methods accept any mix of bed strings, numbers, and `BedTarget` objects.
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
// Bed strings — target specific beds
|
|
91
|
+
board.highlight(['T20', 'D16', 'DB25']);
|
|
92
|
+
|
|
93
|
+
// Segment number — target all scoring beds on that segment
|
|
94
|
+
board.highlight([20]); // double, triple, inner single, outer single
|
|
95
|
+
board.highlight(['20']); // same thing as a string
|
|
96
|
+
|
|
97
|
+
// Miss ring
|
|
98
|
+
board.highlight(['miss']); // highlight entire miss ring
|
|
99
|
+
board.highlight(['M20']); // highlight a specific miss bed
|
|
100
|
+
|
|
101
|
+
// BedTarget object — for precision targeting
|
|
102
|
+
board.highlight([{ segment: 20, ring: 'INNER_SINGLE' }]);
|
|
103
|
+
|
|
104
|
+
// BedTarget without ring — same as segment number
|
|
105
|
+
board.highlight([{ segment: 20 }]);
|
|
106
|
+
|
|
107
|
+
// Custom class name
|
|
108
|
+
board.highlight(['D20'], { className: 'my-highlight' });
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `board.unhighlight(targets, options?): void`
|
|
112
|
+
|
|
113
|
+
Removes a CSS class from specific beds (counterpart to `highlight`).
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
board.unhighlight(['T20']);
|
|
117
|
+
board.unhighlight([20]); // unhighlight all beds on segment 20
|
|
118
|
+
board.unhighlight(['miss']); // unhighlight entire miss ring
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `board.reset(className?): void`
|
|
122
|
+
|
|
123
|
+
Removes highlight class from all beds.
|
|
124
|
+
|
|
125
|
+
### `board.disable(): this`
|
|
126
|
+
|
|
127
|
+
Disables the board — suppresses all throw and hover events, dims the board visually. Chainable.
|
|
128
|
+
|
|
129
|
+
### `board.enable(): this`
|
|
130
|
+
|
|
131
|
+
Re-enables the board after `disable()`. Chainable.
|
|
132
|
+
|
|
133
|
+
### `board.disabled: boolean`
|
|
134
|
+
|
|
135
|
+
Read-only property indicating whether the board is currently disabled.
|
|
136
|
+
|
|
137
|
+
## Bed Strings
|
|
138
|
+
|
|
139
|
+
Beds are identified by an abbreviation prefix and a segment number:
|
|
140
|
+
|
|
141
|
+
| Input | Targets | Example |
|
|
142
|
+
|-------|---------|---------|
|
|
143
|
+
| `T` + number | Triple | `'T20'` |
|
|
144
|
+
| `D` + number | Double | `'D16'` |
|
|
145
|
+
| `S` + number | Both singles (inner + outer) | `'S20'` |
|
|
146
|
+
| `DB` + number | Double Bull (Bullseye) | `'DB25'` |
|
|
147
|
+
| `B` + number | Single Bull (Bull) | `'B25'` |
|
|
148
|
+
| `M` + number | Miss | `'M20'` |
|
|
149
|
+
| number only | All scoring beds on that segment | `'20'` or `20` |
|
|
150
|
+
| `'miss'` | Entire miss ring (all 20 segments) | `'miss'` |
|
|
151
|
+
|
|
152
|
+
When a number is used alone, all scoring beds on that segment are targeted (double, triple, inner single, outer single, single bull, double bull) — the miss ring is excluded. To target a specific single, use the explicit `BedTarget` form: `{ segment: 20, ring: 'INNER_SINGLE' }`.
|
|
153
|
+
|
|
154
|
+
## Events
|
|
155
|
+
|
|
156
|
+
### `throw`
|
|
157
|
+
|
|
158
|
+
Dispatched on the container element when a bed is clicked, tapped, or activated via keyboard.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
interface ThrowDetail {
|
|
162
|
+
bed: string; // e.g. 'T20', 'D16', 'B25', 'DB25', 'M20'
|
|
163
|
+
ring: string; // e.g. 'triple', 'double', 'singleBull', 'doubleBull', 'miss'
|
|
164
|
+
score: number; // e.g. 60, 32, 25, 50, 0
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `hover`
|
|
169
|
+
|
|
170
|
+
Dispatched when a bed is entered or left.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
interface HoverDetail {
|
|
174
|
+
bed: string;
|
|
175
|
+
ring: string;
|
|
176
|
+
score: number;
|
|
177
|
+
hovering: boolean;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Segment States
|
|
182
|
+
|
|
183
|
+
Three built-in state classes are included for marking beds as good, bad, or neutral — useful for coaching sessions, practice games, or heatmaps:
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
// Mark good targets (green)
|
|
187
|
+
board.highlight(['T20'], { className: 'is-good' });
|
|
188
|
+
|
|
189
|
+
// Mark bad targets (red)
|
|
190
|
+
board.highlight(['T1'], { className: 'is-bad' });
|
|
191
|
+
|
|
192
|
+
// Mark neutral targets (blue)
|
|
193
|
+
board.highlight(['D5'], { className: 'is-neutral' });
|
|
194
|
+
|
|
195
|
+
// Remove a specific state
|
|
196
|
+
board.unhighlight(['T1'], { className: 'is-bad' });
|
|
197
|
+
|
|
198
|
+
// Clear all of one state
|
|
199
|
+
board.reset('is-good');
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The colors are themeable via CSS custom properties:
|
|
203
|
+
|
|
204
|
+
```css
|
|
205
|
+
.c-Dartboard {
|
|
206
|
+
--dartboard-good: #228b22;
|
|
207
|
+
--dartboard-bad: #e32636;
|
|
208
|
+
--dartboard-neutral: #4a6fa5;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
You can also define your own state classes. Use this specificity pattern to override the default bed fills:
|
|
213
|
+
|
|
214
|
+
```css
|
|
215
|
+
.c-Dartboard .c-Dartboard-bed.is-warning.isDark,
|
|
216
|
+
.c-Dartboard .c-Dartboard-bed.is-warning.isLight {
|
|
217
|
+
fill: orange;
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Mobile / Touch-Friendly Sizing
|
|
222
|
+
|
|
223
|
+
The default ring proportions can be too narrow for comfortable tapping on small screens. The exported `MOBILE_OPTIONS` preset widens the scoring rings (double, triple, bull) for larger touch targets:
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
import { Dartboard, MOBILE_OPTIONS } from '@tungstenstudio/dartboard-input';
|
|
227
|
+
|
|
228
|
+
const isMobile = window.matchMedia('(max-width: 600px)').matches;
|
|
229
|
+
const board = new Dartboard('#dartboard', isMobile ? MOBILE_OPTIONS : {});
|
|
230
|
+
board.render();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
You can also build your own proportions — the `*Percent` options control each ring's share of the radius (they should sum to 100):
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
new Dartboard('#dartboard', {
|
|
237
|
+
missPercent: 7,
|
|
238
|
+
doublePercent: 14,
|
|
239
|
+
outerSinglePercent: 21,
|
|
240
|
+
triplePercent: 14,
|
|
241
|
+
innerSinglePercent: 26,
|
|
242
|
+
singleBullPercent: 9,
|
|
243
|
+
doubleBullPercent: 9,
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Theming
|
|
248
|
+
|
|
249
|
+
Override CSS custom properties on the `.c-Dartboard` element:
|
|
250
|
+
|
|
251
|
+
```css
|
|
252
|
+
.c-Dartboard {
|
|
253
|
+
--dartboard-dark: #1a1a1a;
|
|
254
|
+
--dartboard-light: #f5f5dc;
|
|
255
|
+
--dartboard-scoring-dark: #e32636;
|
|
256
|
+
--dartboard-scoring-light: #228b22;
|
|
257
|
+
--dartboard-miss: #000;
|
|
258
|
+
--dartboard-miss-label: #fff;
|
|
259
|
+
--dartboard-stroke: silver;
|
|
260
|
+
--dartboard-stroke-width: 2px;
|
|
261
|
+
--dartboard-highlight-color: #ffd700;
|
|
262
|
+
--dartboard-good: #228b22;
|
|
263
|
+
--dartboard-bad: #e32636;
|
|
264
|
+
--dartboard-neutral: #4a6fa5;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Terminology
|
|
269
|
+
|
|
270
|
+
This library uses standard darts terminology:
|
|
271
|
+
|
|
272
|
+
- **Segment** — one of the 20 numbered wedges on the board (1–20)
|
|
273
|
+
- **Bed** — a specific scoring zone: the intersection of a segment and a ring (e.g., "triple 20")
|
|
274
|
+
- **Ring** — a concentric band: double, triple, inner single, outer single, single bull, double bull, miss
|
|
275
|
+
|
|
276
|
+
## Types
|
|
277
|
+
|
|
278
|
+
All types are exported for TypeScript consumers:
|
|
279
|
+
|
|
280
|
+
- `Dartboard` — main class
|
|
281
|
+
- `DartboardOptions` — constructor options
|
|
282
|
+
- `ThrowDetail` — throw event payload
|
|
283
|
+
- `HoverDetail` — hover event payload
|
|
284
|
+
- `BedTarget` — explicit target (`{ segment, ring? }`) — omit `ring` to target all scoring beds
|
|
285
|
+
- `BedInput` — union of `BedTarget | string | number` accepted by all targeting methods
|
|
286
|
+
- `HighlightOptions` — options for `highlight`
|
|
287
|
+
- `Ring`, `Rings`, `Segment`, `Bed` — board model types
|
|
288
|
+
- `DEFAULT_OPTIONS`, `MOBILE_OPTIONS` — ring proportion presets
|
|
289
|
+
- `DEFAULT_RINGS`, `DEFAULT_SEGMENTS` — board layout constants
|
|
290
|
+
- `parseBed` — utility to parse a bed string into `BedTarget[]`
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
ISC
|