split-flap-board 0.0.4 → 0.0.5
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 +165 -105
- package/dist/cjs/SplitFlapBoard.js +7 -12
- package/dist/cjs/lib/board.js +1 -1
- package/dist/cjs/lib/flap.js +1 -1
- package/dist/cjs/spools/SplitFlapSpool.js +2 -2
- package/dist/cjs/spools/SplitFlapSpoolBase.js +3 -3
- package/dist/cjs/spools/SplitFlapSpoolRealistic.js +3 -3
- package/dist/esm/SplitFlapBoard.js +8 -13
- package/dist/esm/lib/board.js +1 -1
- package/dist/esm/lib/flap.js +1 -1
- package/dist/esm/spools/SplitFlapSpool.js +3 -3
- package/dist/esm/spools/SplitFlapSpoolBase.js +7 -7
- package/dist/esm/spools/SplitFlapSpoolRealistic.js +6 -6
- package/dist/types/SplitFlapBoard.d.ts +3 -3
- package/dist/types/lib/board.d.ts +2 -10
- package/dist/types/lib/flap.d.ts +1 -1
- package/dist/types/spools/SplitFlapSpool.d.ts +3 -2
- package/dist/types/spools/SplitFlapSpoolBase.d.ts +6 -3
- package/dist/types/spools/SplitFlapSpoolRealistic.d.ts +4 -4
- package/dist/types/types.d.ts +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,67 +17,154 @@
|
|
|
17
17
|
</a>
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
|
-
Web component
|
|
20
|
+
Web component library for split-flap displays, the mechanical boards found in airports and train stations. Built with [Lit](https://lit.dev/), it works in any framework or plain HTML.
|
|
21
|
+
|
|
22
|
+
- **Simple mental model**: flap -> spool -> board
|
|
23
|
+
- **Framework friendly**: works in plain HTML and can be used from React or other frameworks
|
|
24
|
+
- **Flexible content**: supports character, color, image, and custom flaps
|
|
25
|
+
- **Two built-in looks**: `minimal` and `realistic`
|
|
21
26
|
|
|
22
27
|
## How a Split-Flap Display Works
|
|
23
28
|
|
|
24
29
|
> [How a Split-Flap Display Works (YouTube)](https://www.youtube.com/watch?v=UAQJJAQSg_g)
|
|
25
30
|
|
|
26
|
-
A split-flap display
|
|
31
|
+
A split-flap display, also called a Solari board, works through a simple mechanical loop:
|
|
27
32
|
|
|
28
|
-
- A **spool** (drum) holds a series of **flaps** (thin cards), each printed with the top half of one character on the front and the bottom half of
|
|
33
|
+
- A **spool** (drum) holds a series of **flaps** (thin cards), each printed with the top half of one character on the front and the bottom half of another on the back.
|
|
29
34
|
- A **stepper motor** rotates the spool precisely. As each flap passes vertical, gravity pulls it down, snapping it against a **backstop** and creating the characteristic clacking sound.
|
|
30
|
-
- This reveal happens one flap at a time, so going from `A` to `Z` means cycling through every character in between. The order of the flaps on the spool is fixed
|
|
31
|
-
- A **hall effect sensor** and magnet
|
|
35
|
+
- This reveal happens one flap at a time, so going from `A` to `Z` means cycling through every character in between. The order of the flaps on the spool is fixed.
|
|
36
|
+
- A **hall effect sensor** and magnet give the controller a consistent home position, so it always knows which character is showing even after a power cycle.
|
|
37
|
+
|
|
38
|
+
In code, each `<split-flap-spool>` mirrors that behavior: it holds an ordered sequence of **flaps** and steps forward through them until it reaches a target key.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm add split-flap-board
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Board
|
|
32
49
|
|
|
33
|
-
|
|
50
|
+
```html
|
|
51
|
+
<script type="module">
|
|
52
|
+
import 'split-flap-board';
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<split-flap-board></split-flap-board>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import { fromLines } from 'split-flap-board';
|
|
60
|
+
|
|
61
|
+
const board = document.querySelector('split-flap-board');
|
|
62
|
+
const { spools, grid } = fromLines(['HELLO WORLD'], 11);
|
|
63
|
+
|
|
64
|
+
board.spools = spools;
|
|
65
|
+
board.grid = grid;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For most apps, this is the easiest way to start:
|
|
69
|
+
|
|
70
|
+
1. build `spools` once
|
|
71
|
+
2. update `grid` whenever the displayed text changes
|
|
72
|
+
|
|
73
|
+
### Single Spool
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<script type="module">
|
|
77
|
+
import 'split-flap-board';
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<split-flap-spool value="A"></split-flap-spool>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
const spool = document.querySelector('split-flap-spool');
|
|
85
|
+
spool.value = 'Z'; // steps forward: A -> B -> ... -> Z
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Switch to the realistic look:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<split-flap-spool variant="realistic" value="A"></split-flap-spool>
|
|
92
|
+
```
|
|
34
93
|
|
|
35
94
|
## Core Concepts
|
|
36
95
|
|
|
37
96
|
### Flap
|
|
38
97
|
|
|
39
|
-
A flap is one card on the spool, the
|
|
98
|
+
A flap is one card on the spool, the smallest unit of display content. The library ships with four built-in flap types:
|
|
40
99
|
|
|
41
100
|
```ts
|
|
42
101
|
// Character (default)
|
|
43
|
-
{
|
|
102
|
+
{
|
|
103
|
+
type: 'char';
|
|
104
|
+
key?: string;
|
|
105
|
+
value: string;
|
|
106
|
+
color?: string;
|
|
107
|
+
bg?: string;
|
|
108
|
+
fontSize?: string;
|
|
109
|
+
fontFamily?: string;
|
|
110
|
+
fontWeight?: string;
|
|
111
|
+
}
|
|
44
112
|
|
|
45
113
|
// Solid color
|
|
46
|
-
{
|
|
114
|
+
{
|
|
115
|
+
type: 'color';
|
|
116
|
+
key?: string;
|
|
117
|
+
value: string;
|
|
118
|
+
}
|
|
47
119
|
|
|
48
120
|
// Image
|
|
49
|
-
{
|
|
121
|
+
{
|
|
122
|
+
type: 'image';
|
|
123
|
+
key?: string;
|
|
124
|
+
src: string;
|
|
125
|
+
alt?: string;
|
|
126
|
+
}
|
|
50
127
|
|
|
51
|
-
// Custom
|
|
52
|
-
{
|
|
128
|
+
// Custom, top and bottom halves rendered independently
|
|
129
|
+
{
|
|
130
|
+
type: 'custom';
|
|
131
|
+
key: string;
|
|
132
|
+
top: TemplateResult;
|
|
133
|
+
bottom: TemplateResult;
|
|
134
|
+
}
|
|
53
135
|
```
|
|
54
136
|
|
|
55
|
-
The `key` field is optional on all types except `custom`. When omitted
|
|
137
|
+
The `key` field is optional on all types except `custom`. When omitted, the library uses the natural identifier:
|
|
56
138
|
|
|
57
|
-
|
|
139
|
+
- `value` for `char` and `color`
|
|
140
|
+
- `src` for `image`
|
|
141
|
+
|
|
142
|
+
Char flaps can be styled per flap when needed:
|
|
58
143
|
|
|
59
144
|
```ts
|
|
60
|
-
const
|
|
61
|
-
|
|
145
|
+
const styledSpool = charSpool.map((flap) =>
|
|
146
|
+
flap.type === 'char' ? { ...flap, fontSize: '3rem', color: '#fff', bg: '#2563eb' } : flap
|
|
62
147
|
);
|
|
63
|
-
|
|
148
|
+
|
|
149
|
+
spool.flaps = styledSpool;
|
|
64
150
|
```
|
|
65
151
|
|
|
66
152
|
### Spool
|
|
67
153
|
|
|
68
|
-
A spool is an ordered array of flaps, the sequence a `<split-flap-spool>` steps through. Define it once
|
|
154
|
+
A spool is an ordered array of flaps, the sequence a `<split-flap-spool>` steps through. Define it once and reuse it anywhere.
|
|
69
155
|
|
|
70
156
|
```ts
|
|
71
157
|
type TSpool = TFlap[];
|
|
72
158
|
```
|
|
73
159
|
|
|
74
|
-
The library ships built-in spools:
|
|
160
|
+
The library ships with built-in spools:
|
|
75
161
|
|
|
76
162
|
```ts
|
|
77
|
-
import { charSpool, numericSpool } from 'split-flap-board';
|
|
163
|
+
import { charSpool, colorSpool, numericSpool } from 'split-flap-board';
|
|
78
164
|
|
|
79
|
-
// charSpool
|
|
80
|
-
// numericSpool
|
|
165
|
+
// charSpool -> [' ', A-Z, 0-9, . - / : ]
|
|
166
|
+
// numericSpool -> [' ', 0-9]
|
|
167
|
+
// colorSpool -> named color keys such as 'red', 'green', 'blue'
|
|
81
168
|
```
|
|
82
169
|
|
|
83
170
|
Custom spools are just arrays:
|
|
@@ -91,7 +178,7 @@ const statusSpool: TSpool = [
|
|
|
91
178
|
];
|
|
92
179
|
```
|
|
93
180
|
|
|
94
|
-
You can mix flap types within a spool:
|
|
181
|
+
You can also mix flap types within a single spool:
|
|
95
182
|
|
|
96
183
|
```ts
|
|
97
184
|
const mixedSpool: TSpool = [
|
|
@@ -102,12 +189,17 @@ const mixedSpool: TSpool = [
|
|
|
102
189
|
];
|
|
103
190
|
```
|
|
104
191
|
|
|
105
|
-
### Spools
|
|
192
|
+
### Spools Grid vs. Target Grid
|
|
106
193
|
|
|
107
194
|
A board has two separate grids:
|
|
108
195
|
|
|
109
|
-
- **`spools
|
|
110
|
-
- **`grid
|
|
196
|
+
- **`spools`**: a `TSpool[][]` that defines what each cell can show. This is usually set once.
|
|
197
|
+
- **`grid`**: a `string[][]` of target keys that defines what each cell should show right now. This is the part you usually update at runtime.
|
|
198
|
+
|
|
199
|
+
If you are unsure which one to change, use this rule of thumb:
|
|
200
|
+
|
|
201
|
+
- change `spools` when the available flap set changes
|
|
202
|
+
- change `grid` when the displayed content changes
|
|
111
203
|
|
|
112
204
|
```ts
|
|
113
205
|
import { charSpool, spoolGrid } from 'split-flap-board';
|
|
@@ -115,10 +207,10 @@ import { charSpool, spoolGrid } from 'split-flap-board';
|
|
|
115
207
|
// spoolGrid(spool, cols, rows) fills a uniform TSpool[][]
|
|
116
208
|
board.spools = spoolGrid(charSpool, 10, 3);
|
|
117
209
|
|
|
118
|
-
//
|
|
210
|
+
// Per-column: pass an array of spools, one per column. Shorter arrays repeat.
|
|
119
211
|
board.spools = spoolGrid([charSpool, charSpool, statusSpool], 3, 2);
|
|
120
212
|
|
|
121
|
-
//
|
|
213
|
+
// Fully custom: build the 2D array directly.
|
|
122
214
|
board.spools = [
|
|
123
215
|
[charSpool, charSpool, statusSpool],
|
|
124
216
|
[charSpool, charSpool, statusSpool]
|
|
@@ -135,65 +227,16 @@ Board dimensions are inferred from `spools`.
|
|
|
135
227
|
|
|
136
228
|
## Usage
|
|
137
229
|
|
|
138
|
-
###
|
|
139
|
-
|
|
140
|
-
```html
|
|
141
|
-
<script type="module">
|
|
142
|
-
import 'split-flap-board';
|
|
143
|
-
</script>
|
|
144
|
-
|
|
145
|
-
<split-flap-spool value="A"></split-flap-spool>
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
```js
|
|
149
|
-
const spool = document.querySelector('split-flap-spool');
|
|
150
|
-
spool.value = 'Z'; // steps forward: A → B → ... → Z
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
Switch to the realistic look:
|
|
154
|
-
|
|
155
|
-
```html
|
|
156
|
-
<split-flap-spool variant="realistic" value="A"></split-flap-spool>
|
|
157
|
-
```
|
|
230
|
+
### Updating at Runtime
|
|
158
231
|
|
|
159
|
-
|
|
232
|
+
For content changes, only `grid` needs to change. Assign a new array reference:
|
|
160
233
|
|
|
161
234
|
```js
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
spool.flaps = statusSpool;
|
|
165
|
-
spool.value = 'green';
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Board
|
|
169
|
-
|
|
170
|
-
```html
|
|
171
|
-
<script type="module">
|
|
172
|
-
import 'split-flap-board';
|
|
173
|
-
</script>
|
|
174
|
-
|
|
175
|
-
<split-flap-board></split-flap-board>
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
```js
|
|
179
|
-
import { fromLines } from 'split-flap-board';
|
|
180
|
-
|
|
181
|
-
const board = document.querySelector('split-flap-board');
|
|
182
|
-
const { spools, grid } = fromLines(['HELLO WORLD'], 11);
|
|
183
|
-
board.spools = spools;
|
|
184
|
-
board.grid = grid;
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Updating at runtime
|
|
188
|
-
|
|
189
|
-
Only `grid` needs to change for content updates. Assign a new array reference:
|
|
190
|
-
|
|
191
|
-
```js
|
|
192
|
-
// refresh content - spools stay the same
|
|
235
|
+
// Refresh content, spools stay the same.
|
|
193
236
|
board.grid = [['G', 'O', 'O', 'D', 'B', 'Y', 'E', ' ', ' ', ' ', ' ']];
|
|
194
237
|
```
|
|
195
238
|
|
|
196
|
-
### Multi-
|
|
239
|
+
### Multi-Row Board
|
|
197
240
|
|
|
198
241
|
```js
|
|
199
242
|
import { fromLines } from 'split-flap-board';
|
|
@@ -207,7 +250,20 @@ board.spools = spools;
|
|
|
207
250
|
board.grid = grid;
|
|
208
251
|
```
|
|
209
252
|
|
|
210
|
-
###
|
|
253
|
+
### Custom Spool
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
const statusSpool = [
|
|
257
|
+
{ type: 'color', value: '#111', key: 'off' },
|
|
258
|
+
{ type: 'color', value: '#16a34a', key: 'green' },
|
|
259
|
+
{ type: 'color', value: '#dc2626', key: 'red' }
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
spool.flaps = statusSpool;
|
|
263
|
+
spool.value = 'green';
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Mixed Spools per Column
|
|
211
267
|
|
|
212
268
|
```js
|
|
213
269
|
import { charSpool, spoolGrid } from 'split-flap-board';
|
|
@@ -226,7 +282,7 @@ board.grid = [
|
|
|
226
282
|
];
|
|
227
283
|
```
|
|
228
284
|
|
|
229
|
-
### Colored
|
|
285
|
+
### Colored Rows
|
|
230
286
|
|
|
231
287
|
```js
|
|
232
288
|
import { fromLines } from 'split-flap-board';
|
|
@@ -244,6 +300,8 @@ board.spools = spools;
|
|
|
244
300
|
board.grid = grid;
|
|
245
301
|
```
|
|
246
302
|
|
|
303
|
+
Use row colors when you want text-style boards with a highlighted row, for example boarding status or delays.
|
|
304
|
+
|
|
247
305
|
### React
|
|
248
306
|
|
|
249
307
|
```tsx
|
|
@@ -264,6 +322,7 @@ export function DeparturesBoard() {
|
|
|
264
322
|
|
|
265
323
|
useEffect(() => {
|
|
266
324
|
if (ref.current == null) return;
|
|
325
|
+
|
|
267
326
|
const { spools, grid } = fromLines(['DEPARTURES'], 10);
|
|
268
327
|
(ref.current as any).spools = spools;
|
|
269
328
|
(ref.current as any).grid = grid;
|
|
@@ -273,6 +332,8 @@ export function DeparturesBoard() {
|
|
|
273
332
|
}
|
|
274
333
|
```
|
|
275
334
|
|
|
335
|
+
For richer framework integrations, the simplest approach is usually to keep `spools` stable and only update `grid`.
|
|
336
|
+
|
|
276
337
|
## API Reference
|
|
277
338
|
|
|
278
339
|
### `<split-flap-spool>`
|
|
@@ -298,7 +359,7 @@ The variant elements can also be used directly if you prefer not to use the wrap
|
|
|
298
359
|
|
|
299
360
|
| Property | Type | Default | Description |
|
|
300
361
|
| ------------------ | -------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
301
|
-
| `spools` | `TSpool[][]` |
|
|
362
|
+
| `spools` | `TSpool[][]` | `[]` | 2D spool configuration, one per cell. Board dimensions are inferred from this. Always assign a new array reference to update. |
|
|
302
363
|
| `grid` | `string[][]` | `[]` | 2D array of target keys. Always assign a new array reference to trigger a re-render. |
|
|
303
364
|
| `speed` | `number` | `60` | Flip speed in milliseconds forwarded to every child spool. |
|
|
304
365
|
| `variant` | `'minimal' \| 'realistic'` | `'minimal'` | Visual variant forwarded to every child spool. |
|
|
@@ -333,10 +394,10 @@ function spoolGrid(spool: TSpool | TSpool[], cols: number, rows: number): TSpool
|
|
|
333
394
|
Creates a `TSpool[][]` for use with `board.spools`.
|
|
334
395
|
|
|
335
396
|
```ts
|
|
336
|
-
// Uniform
|
|
397
|
+
// Uniform, same spool for every cell
|
|
337
398
|
spoolGrid(charSpool, 10, 3);
|
|
338
399
|
|
|
339
|
-
// Per-column
|
|
400
|
+
// Per-column, pass an array where index = column. Shorter arrays repeat.
|
|
340
401
|
spoolGrid([charSpool, charSpool, statusSpool], 3, 2);
|
|
341
402
|
```
|
|
342
403
|
|
|
@@ -349,7 +410,7 @@ function fromLines(
|
|
|
349
410
|
): { spools: TSpool[][]; grid: string[][] };
|
|
350
411
|
```
|
|
351
412
|
|
|
352
|
-
Creates a char `spools` grid and a `grid` of target keys from an array of
|
|
413
|
+
Creates a char `spools` grid and a `grid` of target keys from an array of lines. Each line is uppercased, padded with spaces, or truncated to `cols`. Rows with `bg` or `color` get those values baked into their char flaps.
|
|
353
414
|
|
|
354
415
|
```ts
|
|
355
416
|
const { spools, grid } = fromLines(
|
|
@@ -363,7 +424,7 @@ board.grid = grid;
|
|
|
363
424
|
|
|
364
425
|
### CSS Custom Properties
|
|
365
426
|
|
|
366
|
-
Set these on the board to theme all spools at once, or override on individual spools via CSS selectors.
|
|
427
|
+
Set these on the board to theme all spools at once, or override them on individual spools via CSS selectors.
|
|
367
428
|
|
|
368
429
|
```css
|
|
369
430
|
/* Board panel */
|
|
@@ -374,7 +435,7 @@ split-flap-board {
|
|
|
374
435
|
--sfb-gap: 3px; /* gap between spool cells */
|
|
375
436
|
}
|
|
376
437
|
|
|
377
|
-
/* Shared
|
|
438
|
+
/* Shared flap styles */
|
|
378
439
|
split-flap-board {
|
|
379
440
|
--sfb-flap-bg: #111; /* flap background */
|
|
380
441
|
--sfb-flap-color: #f5f0e0; /* flap text color */
|
|
@@ -390,14 +451,14 @@ split-flap-board {
|
|
|
390
451
|
|
|
391
452
|
/* Realistic variant */
|
|
392
453
|
split-flap-board {
|
|
393
|
-
--sfb-spool-width: 1em; /* flap width
|
|
394
|
-
--sfb-spool-height: 2em; /* flap height
|
|
395
|
-
--sfb-drum-radius: 0px; /* cylinder radius
|
|
454
|
+
--sfb-spool-width: 1em; /* flap width, defaults to 1x font-size */
|
|
455
|
+
--sfb-spool-height: 2em; /* flap height, defaults to 2x font-size */
|
|
456
|
+
--sfb-drum-radius: 0px; /* cylinder radius, 0 keeps the flip flat */
|
|
396
457
|
--sfb-crease: 1px; /* gap between the two flap halves */
|
|
397
458
|
--sfb-perspective: 400px; /* CSS perspective depth */
|
|
398
459
|
--sfb-view-transform: none; /* e.g. rotateY(-30deg) */
|
|
399
|
-
--sfb-max-step-angle: 1turn; /* per-step angle cap
|
|
400
|
-
--sfb-flap-border: #2a2a2a; /* border on each flap card
|
|
460
|
+
--sfb-max-step-angle: 1turn; /* per-step angle cap, 8deg tightens small spools */
|
|
461
|
+
--sfb-flap-border: #2a2a2a; /* border on each flap card */
|
|
401
462
|
}
|
|
402
463
|
|
|
403
464
|
/* Per-spool override */
|
|
@@ -409,27 +470,27 @@ split-flap-spool.highlight {
|
|
|
409
470
|
|
|
410
471
|
## Behavior
|
|
411
472
|
|
|
412
|
-
### Initial
|
|
473
|
+
### Initial State
|
|
413
474
|
|
|
414
|
-
Before `value` is set, a `<split-flap-spool>` shows the first flap in its sequence
|
|
475
|
+
Before `value` is set, a `<split-flap-spool>` shows the first flap in its sequence. For `charSpool`, that is a space. This mirrors the physical home position a real board establishes on startup.
|
|
415
476
|
|
|
416
477
|
### Animation
|
|
417
478
|
|
|
418
|
-
Each flap step plays a fold animation where the top half falls away
|
|
479
|
+
Each flap step plays a fold animation where the top half falls away and reveals the next card underneath. The animation duration is derived from `speed`, so it always fits inside one step interval.
|
|
419
480
|
|
|
420
|
-
### Unknown
|
|
481
|
+
### Unknown Key
|
|
421
482
|
|
|
422
|
-
If `value` is set to a key that does not exist in `flaps`, the spool does not start a new search and no error is thrown. If
|
|
483
|
+
If `value` is set to a key that does not exist in `flaps`, the spool does not start a new search and no error is thrown. If this happens during an in-flight animation, the current flip finishes and `settled` reports the flap the spool actually landed on.
|
|
423
484
|
|
|
424
|
-
### Retargeting
|
|
485
|
+
### Retargeting During Motion
|
|
425
486
|
|
|
426
487
|
If `value` changes to another valid key while the spool is already moving, the spool keeps its current forward motion and retargets to the newest valid key. It does not snap backward or restart from the beginning.
|
|
427
488
|
|
|
428
|
-
### Spool
|
|
489
|
+
### Spool Changes During Motion
|
|
429
490
|
|
|
430
491
|
If `flaps` changes while the spool is moving, the component remaps the currently visible flap by key into the new spool, clears stale animation bookkeeping, and continues from the new coherent state.
|
|
431
492
|
|
|
432
|
-
### Grid
|
|
493
|
+
### Grid Size Mismatch
|
|
433
494
|
|
|
434
495
|
If `grid` has more rows or columns than `spools`, the extra entries are ignored. If `grid` is smaller than `spools`, spools without a matching target key stay on their current flap. No errors are thrown.
|
|
435
496
|
|
|
@@ -438,8 +499,8 @@ If `grid` has more rows or columns than `spools`, the extra entries are ignored.
|
|
|
438
499
|
Because a spool only rotates forward, the number of steps depends on the distance ahead in the spool, wrapping around if needed.
|
|
439
500
|
|
|
440
501
|
```
|
|
441
|
-
'A'
|
|
442
|
-
'Z'
|
|
502
|
+
'A' -> 'C' = 2 steps
|
|
503
|
+
'Z' -> 'B' = 3 steps (wraps: Z -> ' ' -> A -> B)
|
|
443
504
|
```
|
|
444
505
|
|
|
445
506
|
This applies to all flap types. Keep the order of your spool in mind when designing update sequences. The closer two keys are in the spool, the faster the transition.
|
|
@@ -449,4 +510,3 @@ This applies to all flap types. Keep the order of your spool in mind when design
|
|
|
449
510
|
- [How a Split-Flap Display Works (YouTube)](https://www.youtube.com/watch?v=UAQJJAQSg_g)
|
|
450
511
|
- [Lit](https://lit.dev/)
|
|
451
512
|
- [Scott Bezek's open-source split-flap hardware](https://github.com/scottbez1/splitflap)
|
|
452
|
-
- [@ybhrdwj on X](https://x.com/ybhrdwj/status/2037110274696896687) - the tweet that started this :)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
${this.spools.map((e
|
|
3
|
-
<div class="board-row" style="z-index: ${
|
|
4
|
-
${
|
|
1
|
+
"use strict";var l=require("lit"),p=require("lit/decorators.js"),n=Object.defineProperty,u=Object.getOwnPropertyDescriptor,a=(d,t,e,s)=>{for(var r=s>1?void 0:s?u(t,e):t,o=d.length-1,i;o>=0;o--)(i=d[o])&&(r=(s?i(t,e,r):i(r))||r);return s&&r&&n(t,e,r),r};exports.SplitFlapBoard=class extends l.LitElement{constructor(){super(...arguments),this.spools=[],this.grid=[],this.speed=60,this.variant="minimal",this.visibleSideCount=-1,this._pendingSettle=!1}updated(t){super.updated(t),(t.has("spools")||t.has("grid"))&&(this._pendingSettle=!0,queueMicrotask(()=>this._checkAllSettled()))}_getSpoolEls(){return Array.from(this.renderRoot.querySelectorAll("split-flap-spool"))}_checkAllSettled(){if(!this._pendingSettle)return;const t=this._getSpoolEls();if(t.length===0){this._pendingSettle=!1,this._dispatchBoardSettled([]);return}t.every(e=>e.isSettled)&&(this._pendingSettle=!1,this._dispatchBoardSettled(t))}_dispatchBoardSettled(t){const e=this._getCurrentGrid(t);this.dispatchEvent(new CustomEvent("board-settled",{detail:{grid:e},bubbles:!0,composed:!0}))}_getCurrentGrid(t){let e=0;return this.spools.map(s=>s.map(()=>{var r,o;const i=(o=(r=t[e])==null?void 0:r.currentValue)!=null?o:"";return e+=1,i}))}render(){return l.html`
|
|
2
|
+
${this.spools.map((t,e)=>l.html`
|
|
3
|
+
<div class="board-row" style="z-index: ${e+1}">
|
|
4
|
+
${t.map((s,r)=>{var o,i;return l.html`
|
|
5
5
|
<split-flap-spool
|
|
6
6
|
.flaps=${s}
|
|
7
|
-
.value=${(
|
|
7
|
+
.value=${(i=(o=this.grid[e])==null?void 0:o[r])!=null?i:""}
|
|
8
8
|
.speed=${this.speed}
|
|
9
9
|
.variant=${this.variant}
|
|
10
10
|
.visibleSideCount=${this.visibleSideCount}
|
|
@@ -13,14 +13,12 @@
|
|
|
13
13
|
`})}
|
|
14
14
|
</div>
|
|
15
15
|
`)}
|
|
16
|
-
`}},exports.SplitFlapBoard.styles=
|
|
16
|
+
`}},exports.SplitFlapBoard.styles=l.css`
|
|
17
17
|
:host {
|
|
18
18
|
display: inline-flex;
|
|
19
19
|
position: relative;
|
|
20
20
|
flex-direction: column;
|
|
21
21
|
box-sizing: border-box;
|
|
22
|
-
/* Inset shadows for side rails; render below all children so frame bars
|
|
23
|
-
* appear at the same visual level. */
|
|
24
22
|
box-shadow:
|
|
25
23
|
inset 14px 0 18px rgba(0, 0, 0, 0.55),
|
|
26
24
|
inset -14px 0 18px rgba(0, 0, 0, 0.55);
|
|
@@ -29,7 +27,6 @@
|
|
|
29
27
|
padding: var(--sfb-board-padding, 10px);
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
/* Absolute overlay so the outer frame ring adds no layout space. */
|
|
33
30
|
:host::after {
|
|
34
31
|
position: absolute;
|
|
35
32
|
z-index: 10000;
|
|
@@ -40,8 +37,6 @@
|
|
|
40
37
|
content: '';
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
/* Frame bar caps the top padding of each row. z-index increases per row in
|
|
44
|
-
* render() so each bar sits above the drum content of the row above it. */
|
|
45
40
|
.board-row {
|
|
46
41
|
display: flex;
|
|
47
42
|
position: relative;
|
|
@@ -58,4 +53,4 @@
|
|
|
58
53
|
height: 10px;
|
|
59
54
|
content: '';
|
|
60
55
|
}
|
|
61
|
-
`,
|
|
56
|
+
`,a([p.property({type:Array})],exports.SplitFlapBoard.prototype,"spools",2),a([p.property({type:Array})],exports.SplitFlapBoard.prototype,"grid",2),a([p.property({type:Number})],exports.SplitFlapBoard.prototype,"speed",2),a([p.property({type:String})],exports.SplitFlapBoard.prototype,"variant",2),a([p.property({type:Number})],exports.SplitFlapBoard.prototype,"visibleSideCount",2),exports.SplitFlapBoard=a([p.customElement("split-flap-board")],exports.SplitFlapBoard);
|
package/dist/cjs/lib/board.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var u=require("../spools/presets.js"),y=Object.defineProperty,i=Object.getOwnPropertySymbols,b=Object.prototype.hasOwnProperty,g=Object.prototype.propertyIsEnumerable,s=(r,t,e)=>t in r?y(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e,c=(r,t)=>{for(var e in t||(t={}))b.call(t,e)&&s(r,e,t[e]);if(i)for(var e of i(t))g.call(t,e)&&s(r,e,t[e]);return r};function h(r,t,e){const o=v(r)?r:[r];return Array.from({length:e},()=>Array.from({length:t},(a,l)=>{var n;return(n=o[l%o.length])!=null?n:u.charSpool}))}function m(r,t){const e=[],o=[];for(const a of r){const l=O(a),n=A(l.text,t).split(""),f=l.bg!=null||l.color!=null;e.push(f?S(t,l):p(u.charSpool,t)),o.push(n)}return{spools:e,grid:o}}function v(r){return Array.isArray(r[0])}function p(r,t){return Array.from({length:t},()=>r)}function d(r,t){return r.type!=="char"?r:c(c(c({},r),t.bg!=null?{bg:t.bg}:{}),t.color!=null?{color:t.color}:{})}function O(r){return typeof r=="string"?{text:r}:r}function A(r,t){return r.toUpperCase().padEnd(t," ").slice(0,t)}function S(r,t){const e=u.charSpool.map(o=>d(o,t));return p(e,r)}exports.fromLines=m,exports.spoolGrid=h;
|
package/dist/cjs/lib/flap.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";function
|
|
1
|
+
"use strict";function c(e){var r,t;switch(e.type){case"char":case"color":return(r=e.key)!=null?r:e.value;case"image":return(t=e.key)!=null?t:e.src;case"custom":return e.key}}exports.getFlapKey=c;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var o=require("lit"),r=require("lit/decorators.js"),S=require("./presets.js"),
|
|
1
|
+
"use strict";var o=require("lit"),r=require("lit/decorators.js"),S=require("./presets.js"),u=require("../lib/flap.js");require("./SplitFlapSpoolMinimal.js"),require("./SplitFlapSpoolRealistic.js");var v=Object.defineProperty,h=Object.getOwnPropertyDescriptor,i=(s,e,t,p)=>{for(var l=p>1?void 0:p?h(e,t):e,a=s.length-1,n;a>=0;a--)(n=s[a])&&(l=(p?n(e,t,l):n(l))||l);return p&&l&&v(e,t,l),l};exports.SplitFlapSpool=class extends o.LitElement{constructor(){super(...arguments),this.variant="minimal",this.value=" ",this.flaps=S.charSpool,this.speed=60,this.visibleSideCount=-1}get currentValue(){var e,t;return(t=(e=this._getActiveSpool())==null?void 0:e.currentValue)!=null?t:this.flaps[0]!=null?u.getFlapKey(this.flaps[0]):void 0}get isSettled(){var e,t;return(t=(e=this._getActiveSpool())==null?void 0:e.isSettled)!=null?t:!0}hasKey(e){var t,p;return(p=(t=this._getActiveSpool())==null?void 0:t.hasKey(e))!=null?p:this.flaps.some(l=>u.getFlapKey(l)===e)}_getActiveSpool(){return this.renderRoot.querySelector("split-flap-spool-minimal, split-flap-spool-realistic")}_renderVariant(){return this.variant==="realistic"?o.html`
|
|
2
2
|
<split-flap-spool-realistic
|
|
3
3
|
.value=${this.value}
|
|
4
4
|
.flaps=${this.flaps}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
.flaps=${this.flaps}
|
|
12
12
|
.speed=${this.speed}
|
|
13
13
|
></split-flap-spool-minimal>
|
|
14
|
-
`}},exports.SplitFlapSpool.styles=o.css`
|
|
14
|
+
`}render(){return this._renderVariant()}},exports.SplitFlapSpool.styles=o.css`
|
|
15
15
|
:host {
|
|
16
16
|
display: contents;
|
|
17
17
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";var n=require("lit"),
|
|
1
|
+
"use strict";var n=require("lit"),p=require("lit/decorators.js"),x=require("lit/directives/style-map.js"),I=require("./presets.js"),o=require("../lib/flap.js"),g=Object.defineProperty,f=Object.getOwnPropertySymbols,y=Object.prototype.hasOwnProperty,T=Object.prototype.propertyIsEnumerable,v=(s,e,t)=>e in s?g(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,_=(s,e)=>{for(var t in e||(e={}))y.call(e,t)&&v(s,t,e[t]);if(f)for(var t of f(e))T.call(e,t)&&v(s,t,e[t]);return s},u=(s,e,t,r)=>{for(var i=void 0,a=s.length-1,h;a>=0;a--)(h=s[a])&&(i=h(e,t,i)||i);return i&&g(e,t,i),i};class l extends n.LitElement{constructor(){super(...arguments),this.value=" ",this.flaps=I.charSpool,this.speed=60,this._currentIndex=0,this._prevIndex=0,this._stepping=!1,this._targetIndex=-1,this._stepTimer=null,this._animTimer=null,this._animEndsAt=0}get _animDur(){return Math.max(Math.floor(this.speed*.85),1)}updated(e){super.updated(e),e.has("flaps")&&this._syncIndicesToFlaps(e.get("flaps")),(e.has("value")||e.has("flaps"))&&this._startStepping()}disconnectedCallback(){super.disconnectedCallback(),this._clearTimers()}get currentFlap(){var e;return(e=this.flaps[this._currentIndex])!=null?e:this.flaps[0]}get currentValue(){return this.currentFlap!=null?o.getFlapKey(this.currentFlap):void 0}get isSettled(){return!this._stepping&&this._targetIndex===-1&&this._stepTimer==null&&this._animTimer==null}hasKey(e){return this._findFlapIndex(e)>=0}_startStepping(){if(!this.flaps.length){this._resetForEmptyFlaps();return}const e=this._findFlapIndex(this.value),t=this._isAnimating();if(e===-1){this._targetIndex=-1,t&&this._scheduleAdvanceOrSettle(this._getRemainingAnimationTime());return}if(this._targetIndex=e,this._currentIndex===this._targetIndex){t?this._scheduleAdvanceOrSettle(this._getRemainingAnimationTime()):this._targetIndex=-1;return}t||this._doStep()}_doStep(){if(!this.flaps.length){this._resetForEmptyFlaps();return}const e=this._getNextIndex();this._prevIndex=this._currentIndex,this._currentIndex=e,this._stepping=!0,this._animEndsAt=Date.now()+this._animDur,this._animTimer!=null&&clearTimeout(this._animTimer),this._animTimer=setTimeout(()=>{this._stepping=!1,this._animTimer=null,this._animEndsAt=0},this._animDur);const t=this._currentIndex!==this._targetIndex?this.speed:this._getRemainingAnimationTime();this._scheduleAdvanceOrSettle(t)}_scheduleAdvanceOrSettle(e){this._stepTimer!=null&&clearTimeout(this._stepTimer),this._stepTimer=setTimeout(()=>{if(this._stepTimer=null,this._targetIndex!==-1&&this._currentIndex!==this._targetIndex){this._doStep();return}this._finishSettling()},Math.max(e,0))}_finishSettling(){if(this._stepping){this._scheduleAdvanceOrSettle(this._getRemainingAnimationTime());return}this._targetIndex=-1;const e=this.currentValue;e!=null&&this.dispatchEvent(new CustomEvent("settled",{detail:{value:e},bubbles:!0,composed:!0}))}_findFlapIndex(e){return this.flaps.findIndex(t=>o.getFlapKey(t)===e)}_getNextIndex(){return(this._currentIndex+1)%this.flaps.length}_isAnimating(){return this._stepping||this._stepTimer!=null}_getRemainingAnimationTime(){return this._stepping?Math.max(this._animEndsAt-Date.now(),1):0}_resetForEmptyFlaps(){this._clearTimers(),this._targetIndex=-1,this._currentIndex=0,this._prevIndex=0,this._stepping=!1}_syncIndicesToFlaps(e){var t,r;if(!this.flaps.length){this._resetForEmptyFlaps();return}if(e==null||!e.length){this._currentIndex=0,this._prevIndex=0,this._targetIndex=-1,this._clearTimers(),this._stepping=!1;return}const i=(t=e[this._currentIndex])!=null?t:e[0],a=(r=e[this._prevIndex])!=null?r:i,h=i!=null?o.getFlapKey(i):void 0,d=a!=null?o.getFlapKey(a):h;this._clearTimers(),this._stepping=!1,this._targetIndex=-1;const c=h!=null?this._findFlapIndex(h):-1,m=d!=null?this._findFlapIndex(d):-1;this._currentIndex=c>=0?c:0,this._prevIndex=m>=0?m:this._currentIndex}_clearTimers(){this._stepTimer!=null&&(clearTimeout(this._stepTimer),this._stepTimer=null),this._animTimer!=null&&(clearTimeout(this._animTimer),this._animTimer=null),this._animEndsAt=0}_renderHalf(e,t){var r;switch(e.type){case"char":return n.html`
|
|
2
2
|
<div
|
|
3
3
|
class="char-inner"
|
|
4
|
-
style=${
|
|
4
|
+
style=${x.styleMap(_(_(_(_(_({},e.color!=null?{color:e.color}:{}),e.bg!=null?{background:e.bg}:{}),e.fontSize!=null?{fontSize:e.fontSize}:{}),e.fontFamily!=null?{fontFamily:e.fontFamily}:{}),e.fontWeight!=null?{fontWeight:e.fontWeight}:{}))}
|
|
5
5
|
>
|
|
6
6
|
${e.value}
|
|
7
7
|
</div>
|
|
@@ -13,4 +13,4 @@
|
|
|
13
13
|
<div class="half top flipping">${this._renderHalf(r,"top")}</div>
|
|
14
14
|
<div class="half bottom flipping">${this._renderHalf(t,"bottom")}</div>
|
|
15
15
|
`:n.nothing}
|
|
16
|
-
`}}
|
|
16
|
+
`}}u([p.property({type:String})],l.prototype,"value"),u([p.property({type:Array})],l.prototype,"flaps"),u([p.property({type:Number})],l.prototype,"speed"),u([p.state()],l.prototype,"_currentIndex"),u([p.state()],l.prototype,"_prevIndex"),u([p.state()],l.prototype,"_stepping"),exports.SplitFlapSpoolBase=l;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
"use strict";var p=require("lit"),f=require("lit/decorators.js"),d=require("lit/directives/ref.js"),u=require("lit/directives/style-map.js");require("./presets.js");var v=require("../lib/spool-layout.js"),m=require("./SplitFlapSpoolBase.js"),b=Object.defineProperty,g=Object.getOwnPropertyDescriptor,h=(n,e,
|
|
1
|
+
"use strict";var p=require("lit"),f=require("lit/decorators.js"),d=require("lit/directives/ref.js"),u=require("lit/directives/style-map.js");require("./presets.js");var v=require("../lib/spool-layout.js"),m=require("./SplitFlapSpoolBase.js"),b=Object.defineProperty,g=Object.getOwnPropertyDescriptor,h=(n,e,r,t)=>{for(var a=t>1?void 0:t?g(e,r):e,s=n.length-1,i;s>=0;s--)(i=n[s])&&(a=(t?i(e,r,a):i(a))||a);return t&&a&&b(e,r,a),a},x=(n,e,r)=>new Promise((t,a)=>{var s=l=>{try{o(r.next(l))}catch(c){a(c)}},i=l=>{try{o(r.throw(l))}catch(c){a(c)}},o=l=>l.done?t(l.value):Promise.resolve(l.value).then(s,i);o((r=r.apply(n,e)).next())});exports.SplitFlapSpoolRealistic=class extends m.SplitFlapSpoolBase{constructor(){super(...arguments),this.visibleSideCount=-1,this._slotRef=d.createRef(),this._wrapResetTimer=null,this._skipNextIndexAnimation=!1}firstUpdated(){this._syncSlotState()}updated(e){var r;const t=this._currentIndex,a=this._prevIndex;super.updated(e),e.has("flaps")&&this._handleFlapChange(t,a),e.has("_currentIndex")&&this._handleIndexChange((r=e.get("_currentIndex"))!=null?r:0)}disconnectedCallback(){super.disconnectedCallback(),this._clearWrapTimer()}_clearWrapTimer(){this._wrapResetTimer!=null&&(clearTimeout(this._wrapResetTimer),this._wrapResetTimer=null)}_syncSlotState(){const e=this._slotRef.value;e!=null&&(e.style.setProperty("--current-character-index",String(this._currentIndex)),e.style.setProperty("--_flip-dur","0ms"))}_handleFlapChange(e,r){this._clearWrapTimer();const t=this._currentIndex!==e||this._prevIndex!==r;this._skipNextIndexAnimation=t,this._syncSlotState()}_handleIndexChange(e){if(this._skipNextIndexAnimation){this._skipNextIndexAnimation=!1,this._syncSlotState();return}this._animateStep(e,this._currentIndex)}_waitForPositionReset(){return new Promise(e=>{requestAnimationFrame(()=>{requestAnimationFrame(()=>e())})})}_isWrapForwardStep(e,r){return e===this.flaps.length-1&&r===0}_animateStep(e,r){return x(this,null,function*(){const t=this._slotRef.value;if(t==null)return;this._clearWrapTimer(),t.style.setProperty("--_flip-dur","0ms"),t.style.setProperty("--current-character-index",String(e)),yield this._waitForPositionReset();const a=this._isWrapForwardStep(e,r),s=a?this.flaps.length:r;t.style.setProperty("--_flip-dur",`${this._animDur}ms`),t.style.setProperty("--current-character-index",String(s)),a&&(this._wrapResetTimer=setTimeout(()=>{t.style.setProperty("--_flip-dur","0ms"),t.style.setProperty("--current-character-index","0"),this._wrapResetTimer=null,this.requestUpdate()},this._animDur))})}_getRenderCenter(){return this.flaps.length>0&&this._prevIndex===this.flaps.length-1&&this._currentIndex===0&&(this._stepping||this._wrapResetTimer!=null)?this.flaps.length:this._currentIndex}render(){const e=this._getRenderCenter(),r=v.getRenderedFlaps(this.flaps,this._currentIndex,{visibleSideCount:this.visibleSideCount,renderCenter:e});return p.html`
|
|
2
2
|
<div
|
|
3
3
|
${d.ref(this._slotRef)}
|
|
4
4
|
class="slot"
|
|
5
5
|
style=${u.styleMap({"--total":String(this.flaps.length)})}
|
|
6
6
|
>
|
|
7
|
-
${
|
|
7
|
+
${r.map(({flap:t,actualIndex:a,renderedIndex:s})=>{const i=this.flaps.length>2&&this.flaps.length%2===0&&Math.abs(s-e)===this.flaps.length/2;return p.html`
|
|
8
8
|
<div
|
|
9
9
|
class="character ${i?"is-background":""}"
|
|
10
10
|
style="--index: ${s}"
|
|
11
|
-
data-index=${
|
|
11
|
+
data-index=${a}
|
|
12
12
|
>
|
|
13
13
|
<div class="flap">${this._renderHalf(t,"top")}</div>
|
|
14
14
|
<div class="flap" aria-hidden="true">${this._renderHalf(t,"bottom")}</div>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{css as b,LitElement as
|
|
2
|
-
${this.spools.map((e
|
|
3
|
-
<div class="board-row" style="z-index: ${
|
|
4
|
-
${
|
|
1
|
+
import{css as b,LitElement as u,html as n}from"lit";import{property as d,customElement as c}from"lit/decorators.js";var h=Object.defineProperty,g=Object.getOwnPropertyDescriptor,a=(t,e,p,s)=>{for(var r=s>1?void 0:s?g(e,p):e,i=t.length-1,l;i>=0;i--)(l=t[i])&&(r=(s?l(e,p,r):l(r))||r);return s&&r&&h(e,p,r),r};let o=class extends u{constructor(){super(...arguments),this.spools=[],this.grid=[],this.speed=60,this.variant="minimal",this.visibleSideCount=-1,this._pendingSettle=!1}updated(t){super.updated(t),(t.has("spools")||t.has("grid"))&&(this._pendingSettle=!0,queueMicrotask(()=>this._checkAllSettled()))}_getSpoolEls(){return Array.from(this.renderRoot.querySelectorAll("split-flap-spool"))}_checkAllSettled(){if(!this._pendingSettle)return;const t=this._getSpoolEls();if(t.length===0){this._pendingSettle=!1,this._dispatchBoardSettled([]);return}t.every(e=>e.isSettled)&&(this._pendingSettle=!1,this._dispatchBoardSettled(t))}_dispatchBoardSettled(t){const e=this._getCurrentGrid(t);this.dispatchEvent(new CustomEvent("board-settled",{detail:{grid:e},bubbles:!0,composed:!0}))}_getCurrentGrid(t){let e=0;return this.spools.map(p=>p.map(()=>{var s,r;const i=(r=(s=t[e])==null?void 0:s.currentValue)!=null?r:"";return e+=1,i}))}render(){return n`
|
|
2
|
+
${this.spools.map((t,e)=>n`
|
|
3
|
+
<div class="board-row" style="z-index: ${e+1}">
|
|
4
|
+
${t.map((p,s)=>{var r,i;return n`
|
|
5
5
|
<split-flap-spool
|
|
6
|
-
.flaps=${
|
|
7
|
-
.value=${(
|
|
6
|
+
.flaps=${p}
|
|
7
|
+
.value=${(i=(r=this.grid[e])==null?void 0:r[s])!=null?i:""}
|
|
8
8
|
.speed=${this.speed}
|
|
9
9
|
.variant=${this.variant}
|
|
10
10
|
.visibleSideCount=${this.visibleSideCount}
|
|
@@ -13,14 +13,12 @@ import{css as b,LitElement as h,html as n}from"lit";import{property as p,customE
|
|
|
13
13
|
`})}
|
|
14
14
|
</div>
|
|
15
15
|
`)}
|
|
16
|
-
`}};
|
|
16
|
+
`}};o.styles=b`
|
|
17
17
|
:host {
|
|
18
18
|
display: inline-flex;
|
|
19
19
|
position: relative;
|
|
20
20
|
flex-direction: column;
|
|
21
21
|
box-sizing: border-box;
|
|
22
|
-
/* Inset shadows for side rails; render below all children so frame bars
|
|
23
|
-
* appear at the same visual level. */
|
|
24
22
|
box-shadow:
|
|
25
23
|
inset 14px 0 18px rgba(0, 0, 0, 0.55),
|
|
26
24
|
inset -14px 0 18px rgba(0, 0, 0, 0.55);
|
|
@@ -29,7 +27,6 @@ import{css as b,LitElement as h,html as n}from"lit";import{property as p,customE
|
|
|
29
27
|
padding: var(--sfb-board-padding, 10px);
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
/* Absolute overlay so the outer frame ring adds no layout space. */
|
|
33
30
|
:host::after {
|
|
34
31
|
position: absolute;
|
|
35
32
|
z-index: 10000;
|
|
@@ -40,8 +37,6 @@ import{css as b,LitElement as h,html as n}from"lit";import{property as p,customE
|
|
|
40
37
|
content: '';
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
/* Frame bar caps the top padding of each row. z-index increases per row in
|
|
44
|
-
* render() so each bar sits above the drum content of the row above it. */
|
|
45
40
|
.board-row {
|
|
46
41
|
display: flex;
|
|
47
42
|
position: relative;
|
|
@@ -58,4 +53,4 @@ import{css as b,LitElement as h,html as n}from"lit";import{property as p,customE
|
|
|
58
53
|
height: 10px;
|
|
59
54
|
content: '';
|
|
60
55
|
}
|
|
61
|
-
`,
|
|
56
|
+
`,a([d({type:Array})],o.prototype,"spools",2),a([d({type:Array})],o.prototype,"grid",2),a([d({type:Number})],o.prototype,"speed",2),a([d({type:String})],o.prototype,"variant",2),a([d({type:Number})],o.prototype,"visibleSideCount",2),o=a([c("split-flap-board")],o);export{o as SplitFlapBoard};
|
package/dist/esm/lib/board.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{charSpool as
|
|
1
|
+
import{charSpool as u}from"../spools/presets.js";var y=Object.defineProperty,f=Object.getOwnPropertySymbols,b=Object.prototype.hasOwnProperty,g=Object.prototype.propertyIsEnumerable,i=(r,t,e)=>t in r?y(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e,c=(r,t)=>{for(var e in t||(t={}))b.call(t,e)&&i(r,e,t[e]);if(f)for(var e of f(t))g.call(t,e)&&i(r,e,t[e]);return r};function m(r,t,e){const o=v(r)?r:[r];return Array.from({length:e},()=>Array.from({length:t},(a,n)=>{var l;return(l=o[n%o.length])!=null?l:u}))}function h(r,t){const e=[],o=[];for(const a of r){const n=d(a),l=A(n.text,t).split(""),s=n.bg!=null||n.color!=null;e.push(s?j(t,n):p(u,t)),o.push(l)}return{spools:e,grid:o}}function v(r){return Array.isArray(r[0])}function p(r,t){return Array.from({length:t},()=>r)}function O(r,t){return r.type!=="char"?r:c(c(c({},r),t.bg!=null?{bg:t.bg}:{}),t.color!=null?{color:t.color}:{})}function d(r){return typeof r=="string"?{text:r}:r}function A(r,t){return r.toUpperCase().padEnd(t," ").slice(0,t)}function j(r,t){const e=u.map(o=>O(o,t));return p(e,r)}export{h as fromLines,m as spoolGrid};
|
package/dist/esm/lib/flap.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function r
|
|
1
|
+
function t(r){var e,c;switch(r.type){case"char":case"color":return(e=r.key)!=null?e:r.value;case"image":return(c=r.key)!=null?c:r.src;case"custom":return r.key}}export{t as getFlapKey};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{css as
|
|
1
|
+
import{css as h,LitElement as m,html as u}from"lit";import{property as o,customElement as d}from"lit/decorators.js";import{charSpool as c}from"./presets.js";import{getFlapKey as v}from"../lib/flap.js";import"./SplitFlapSpoolMinimal.js";import"./SplitFlapSpoolRealistic.js";var f=Object.defineProperty,y=Object.getOwnPropertyDescriptor,p=(e,t,i,s)=>{for(var r=s>1?void 0:s?y(t,i):t,a=e.length-1,n;a>=0;a--)(n=e[a])&&(r=(s?n(t,i,r):n(r))||r);return s&&r&&f(t,i,r),r};let l=class extends m{constructor(){super(...arguments),this.variant="minimal",this.value=" ",this.flaps=c,this.speed=60,this.visibleSideCount=-1}get currentValue(){var e,t;return(t=(e=this._getActiveSpool())==null?void 0:e.currentValue)!=null?t:this.flaps[0]!=null?v(this.flaps[0]):void 0}get isSettled(){var e,t;return(t=(e=this._getActiveSpool())==null?void 0:e.isSettled)!=null?t:!0}hasKey(e){var t,i;return(i=(t=this._getActiveSpool())==null?void 0:t.hasKey(e))!=null?i:this.flaps.some(s=>v(s)===e)}_getActiveSpool(){return this.renderRoot.querySelector("split-flap-spool-minimal, split-flap-spool-realistic")}_renderVariant(){return this.variant==="realistic"?u`
|
|
2
2
|
<split-flap-spool-realistic
|
|
3
3
|
.value=${this.value}
|
|
4
4
|
.flaps=${this.flaps}
|
|
@@ -11,8 +11,8 @@ import{css as m,LitElement as h,html as u}from"lit";import{property as o,customE
|
|
|
11
11
|
.flaps=${this.flaps}
|
|
12
12
|
.speed=${this.speed}
|
|
13
13
|
></split-flap-spool-minimal>
|
|
14
|
-
`}};l.styles=
|
|
14
|
+
`}render(){return this._renderVariant()}};l.styles=h`
|
|
15
15
|
:host {
|
|
16
16
|
display: contents;
|
|
17
17
|
}
|
|
18
|
-
`,p([o({type:String})],l.prototype,"variant",2),p([o({type:String})],l.prototype,"value",2),p([o({type:Array})],l.prototype,"flaps",2),p([o({type:Number})],l.prototype,"speed",2),p([o({type:Number})],l.prototype,"visibleSideCount",2),l=p([
|
|
18
|
+
`,p([o({type:String})],l.prototype,"variant",2),p([o({type:String})],l.prototype,"value",2),p([o({type:Array})],l.prototype,"flaps",2),p([o({type:Number})],l.prototype,"speed",2),p([o({type:Number})],l.prototype,"visibleSideCount",2),l=p([d("split-flap-spool")],l);export{l as SplitFlapSpool};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import{LitElement as
|
|
1
|
+
import{LitElement as T,html as h,nothing as f}from"lit";import{property as _,state as d}from"lit/decorators.js";import{styleMap as y}from"lit/directives/style-map.js";import{charSpool as F}from"./presets.js";import{getFlapKey as o}from"../lib/flap.js";var v=Object.defineProperty,x=Object.getOwnPropertySymbols,b=Object.prototype.hasOwnProperty,S=Object.prototype.propertyIsEnumerable,I=(s,t,e)=>t in s?v(s,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[t]=e,u=(s,t)=>{for(var e in t||(t={}))b.call(t,e)&&I(s,e,t[e]);if(x)for(var e of x(t))S.call(t,e)&&I(s,e,t[e]);return s},p=(s,t,e,n)=>{for(var i=void 0,l=s.length-1,a;l>=0;l--)(a=s[l])&&(i=a(t,e,i)||i);return i&&v(t,e,i),i};class r extends T{constructor(){super(...arguments),this.value=" ",this.flaps=F,this.speed=60,this._currentIndex=0,this._prevIndex=0,this._stepping=!1,this._targetIndex=-1,this._stepTimer=null,this._animTimer=null,this._animEndsAt=0}get _animDur(){return Math.max(Math.floor(this.speed*.85),1)}updated(t){super.updated(t),t.has("flaps")&&this._syncIndicesToFlaps(t.get("flaps")),(t.has("value")||t.has("flaps"))&&this._startStepping()}disconnectedCallback(){super.disconnectedCallback(),this._clearTimers()}get currentFlap(){var t;return(t=this.flaps[this._currentIndex])!=null?t:this.flaps[0]}get currentValue(){return this.currentFlap!=null?o(this.currentFlap):void 0}get isSettled(){return!this._stepping&&this._targetIndex===-1&&this._stepTimer==null&&this._animTimer==null}hasKey(t){return this._findFlapIndex(t)>=0}_startStepping(){if(!this.flaps.length){this._resetForEmptyFlaps();return}const t=this._findFlapIndex(this.value),e=this._isAnimating();if(t===-1){this._targetIndex=-1,e&&this._scheduleAdvanceOrSettle(this._getRemainingAnimationTime());return}if(this._targetIndex=t,this._currentIndex===this._targetIndex){e?this._scheduleAdvanceOrSettle(this._getRemainingAnimationTime()):this._targetIndex=-1;return}e||this._doStep()}_doStep(){if(!this.flaps.length){this._resetForEmptyFlaps();return}const t=this._getNextIndex();this._prevIndex=this._currentIndex,this._currentIndex=t,this._stepping=!0,this._animEndsAt=Date.now()+this._animDur,this._animTimer!=null&&clearTimeout(this._animTimer),this._animTimer=setTimeout(()=>{this._stepping=!1,this._animTimer=null,this._animEndsAt=0},this._animDur);const e=this._currentIndex!==this._targetIndex?this.speed:this._getRemainingAnimationTime();this._scheduleAdvanceOrSettle(e)}_scheduleAdvanceOrSettle(t){this._stepTimer!=null&&clearTimeout(this._stepTimer),this._stepTimer=setTimeout(()=>{if(this._stepTimer=null,this._targetIndex!==-1&&this._currentIndex!==this._targetIndex){this._doStep();return}this._finishSettling()},Math.max(t,0))}_finishSettling(){if(this._stepping){this._scheduleAdvanceOrSettle(this._getRemainingAnimationTime());return}this._targetIndex=-1;const t=this.currentValue;t!=null&&this.dispatchEvent(new CustomEvent("settled",{detail:{value:t},bubbles:!0,composed:!0}))}_findFlapIndex(t){return this.flaps.findIndex(e=>o(e)===t)}_getNextIndex(){return(this._currentIndex+1)%this.flaps.length}_isAnimating(){return this._stepping||this._stepTimer!=null}_getRemainingAnimationTime(){return this._stepping?Math.max(this._animEndsAt-Date.now(),1):0}_resetForEmptyFlaps(){this._clearTimers(),this._targetIndex=-1,this._currentIndex=0,this._prevIndex=0,this._stepping=!1}_syncIndicesToFlaps(t){var e,n;if(!this.flaps.length){this._resetForEmptyFlaps();return}if(t==null||!t.length){this._currentIndex=0,this._prevIndex=0,this._targetIndex=-1,this._clearTimers(),this._stepping=!1;return}const i=(e=t[this._currentIndex])!=null?e:t[0],l=(n=t[this._prevIndex])!=null?n:i,a=i!=null?o(i):void 0,m=l!=null?o(l):a;this._clearTimers(),this._stepping=!1,this._targetIndex=-1;const c=a!=null?this._findFlapIndex(a):-1,g=m!=null?this._findFlapIndex(m):-1;this._currentIndex=c>=0?c:0,this._prevIndex=g>=0?g:this._currentIndex}_clearTimers(){this._stepTimer!=null&&(clearTimeout(this._stepTimer),this._stepTimer=null),this._animTimer!=null&&(clearTimeout(this._animTimer),this._animTimer=null),this._animEndsAt=0}_renderHalf(t,e){var n;switch(t.type){case"char":return h`
|
|
2
2
|
<div
|
|
3
3
|
class="char-inner"
|
|
4
|
-
style=${
|
|
4
|
+
style=${y(u(u(u(u(u({},t.color!=null?{color:t.color}:{}),t.bg!=null?{background:t.bg}:{}),t.fontSize!=null?{fontSize:t.fontSize}:{}),t.fontFamily!=null?{fontFamily:t.fontFamily}:{}),t.fontWeight!=null?{fontWeight:t.fontWeight}:{}))}
|
|
5
5
|
>
|
|
6
6
|
${t.value}
|
|
7
7
|
</div>
|
|
8
|
-
`;case"color":return
|
|
8
|
+
`;case"color":return h`<div class="color-fill" style="background: ${t.value}"></div>`;case"image":return h`<img class="image-fill" src=${t.src} alt=${(n=t.alt)!=null?n:""} />`;case"custom":return h`<div class="custom-fill">${t[e]}</div>`}}_getFlaps(){var t;const e=this.currentFlap;return e==null?null:{current:e,prev:(t=this.flaps[this._prevIndex])!=null?t:e}}_renderCard(){const t=this._getFlaps();if(t==null)return f;const{current:e,prev:n}=t,i=this._stepping?n:e;return h`
|
|
9
9
|
<div class="half top">${this._renderHalf(e,"top")}</div>
|
|
10
10
|
<div class="half bottom">${this._renderHalf(i,"bottom")}</div>
|
|
11
11
|
|
|
12
|
-
${this._stepping?
|
|
13
|
-
<div class="half top flipping">${this._renderHalf(
|
|
12
|
+
${this._stepping?h`
|
|
13
|
+
<div class="half top flipping">${this._renderHalf(n,"top")}</div>
|
|
14
14
|
<div class="half bottom flipping">${this._renderHalf(e,"bottom")}</div>
|
|
15
|
-
`:
|
|
16
|
-
`}}
|
|
15
|
+
`:f}
|
|
16
|
+
`}}p([_({type:String})],r.prototype,"value"),p([_({type:Array})],r.prototype,"flaps"),p([_({type:Number})],r.prototype,"speed"),p([d()],r.prototype,"_currentIndex"),p([d()],r.prototype,"_prevIndex"),p([d()],r.prototype,"_stepping");export{r as SplitFlapSpoolBase};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import{css as h,html as p}from"lit";import{property as u,customElement as m}from"lit/decorators.js";import{createRef as v,ref as b}from"lit/directives/ref.js";import{styleMap as
|
|
1
|
+
import{css as h,html as p}from"lit";import{property as u,customElement as m}from"lit/decorators.js";import{createRef as v,ref as b}from"lit/directives/ref.js";import{styleMap as g}from"lit/directives/style-map.js";import"./presets.js";import{getRenderedFlaps as x}from"../lib/spool-layout.js";import{SplitFlapSpoolBase as _}from"./SplitFlapSpoolBase.js";var y=Object.defineProperty,w=Object.getOwnPropertyDescriptor,d=(e,r,t,s)=>{for(var a=s>1?void 0:s?w(r,t):r,i=e.length-1,l;i>=0;i--)(l=e[i])&&(a=(s?l(r,t,a):l(a))||a);return s&&a&&y(r,t,a),a},S=(e,r,t)=>new Promise((s,a)=>{var i=n=>{try{c(t.next(n))}catch(f){a(f)}},l=n=>{try{c(t.throw(n))}catch(f){a(f)}},c=n=>n.done?s(n.value):Promise.resolve(n.value).then(i,l);c((t=t.apply(e,r)).next())});let o=class extends _{constructor(){super(...arguments),this.visibleSideCount=-1,this._slotRef=v(),this._wrapResetTimer=null,this._skipNextIndexAnimation=!1}firstUpdated(){this._syncSlotState()}updated(e){var r;const t=this._currentIndex,s=this._prevIndex;super.updated(e),e.has("flaps")&&this._handleFlapChange(t,s),e.has("_currentIndex")&&this._handleIndexChange((r=e.get("_currentIndex"))!=null?r:0)}disconnectedCallback(){super.disconnectedCallback(),this._clearWrapTimer()}_clearWrapTimer(){this._wrapResetTimer!=null&&(clearTimeout(this._wrapResetTimer),this._wrapResetTimer=null)}_syncSlotState(){const e=this._slotRef.value;e!=null&&(e.style.setProperty("--current-character-index",String(this._currentIndex)),e.style.setProperty("--_flip-dur","0ms"))}_handleFlapChange(e,r){this._clearWrapTimer();const t=this._currentIndex!==e||this._prevIndex!==r;this._skipNextIndexAnimation=t,this._syncSlotState()}_handleIndexChange(e){if(this._skipNextIndexAnimation){this._skipNextIndexAnimation=!1,this._syncSlotState();return}this._animateStep(e,this._currentIndex)}_waitForPositionReset(){return new Promise(e=>{requestAnimationFrame(()=>{requestAnimationFrame(()=>e())})})}_isWrapForwardStep(e,r){return e===this.flaps.length-1&&r===0}_animateStep(e,r){return S(this,null,function*(){const t=this._slotRef.value;if(t==null)return;this._clearWrapTimer(),t.style.setProperty("--_flip-dur","0ms"),t.style.setProperty("--current-character-index",String(e)),yield this._waitForPositionReset();const s=this._isWrapForwardStep(e,r),a=s?this.flaps.length:r;t.style.setProperty("--_flip-dur",`${this._animDur}ms`),t.style.setProperty("--current-character-index",String(a)),s&&(this._wrapResetTimer=setTimeout(()=>{t.style.setProperty("--_flip-dur","0ms"),t.style.setProperty("--current-character-index","0"),this._wrapResetTimer=null,this.requestUpdate()},this._animDur))})}_getRenderCenter(){return this.flaps.length>0&&this._prevIndex===this.flaps.length-1&&this._currentIndex===0&&(this._stepping||this._wrapResetTimer!=null)?this.flaps.length:this._currentIndex}render(){const e=this._getRenderCenter(),r=x(this.flaps,this._currentIndex,{visibleSideCount:this.visibleSideCount,renderCenter:e});return p`
|
|
2
2
|
<div
|
|
3
3
|
${b(this._slotRef)}
|
|
4
4
|
class="slot"
|
|
5
|
-
style=${
|
|
5
|
+
style=${g({"--total":String(this.flaps.length)})}
|
|
6
6
|
>
|
|
7
|
-
${
|
|
7
|
+
${r.map(({flap:t,actualIndex:s,renderedIndex:a})=>{const i=this.flaps.length>2&&this.flaps.length%2===0&&Math.abs(a-e)===this.flaps.length/2;return p`
|
|
8
8
|
<div
|
|
9
9
|
class="character ${i?"is-background":""}"
|
|
10
|
-
style="--index: ${
|
|
10
|
+
style="--index: ${a}"
|
|
11
11
|
data-index=${s}
|
|
12
12
|
>
|
|
13
|
-
<div class="flap">${this._renderHalf(
|
|
14
|
-
<div class="flap" aria-hidden="true">${this._renderHalf(
|
|
13
|
+
<div class="flap">${this._renderHalf(t,"top")}</div>
|
|
14
|
+
<div class="flap" aria-hidden="true">${this._renderHalf(t,"bottom")}</div>
|
|
15
15
|
</div>
|
|
16
16
|
`})}
|
|
17
17
|
</div>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
-
import './
|
|
3
|
-
import type { TSpool } from './types';
|
|
2
|
+
import type { TSplitFlapVariant, TSpool } from './types';
|
|
4
3
|
export declare class SplitFlapBoard extends LitElement {
|
|
5
4
|
static readonly styles: import("lit").CSSResult;
|
|
6
5
|
/** 2-D grid of spool configs; defines what each position can show. Row-major order. */
|
|
@@ -10,7 +9,7 @@ export declare class SplitFlapBoard extends LitElement {
|
|
|
10
9
|
/** Flip speed in ms, forwarded to every child spool. */
|
|
11
10
|
speed: number;
|
|
12
11
|
/** Visual variant forwarded to every child spool. */
|
|
13
|
-
variant:
|
|
12
|
+
variant: TSplitFlapVariant;
|
|
14
13
|
/** Number of visible drum sides forwarded to every child spool (-1 = default). */
|
|
15
14
|
visibleSideCount: number;
|
|
16
15
|
/** True while at least one spool is still animating toward its target. */
|
|
@@ -19,6 +18,7 @@ export declare class SplitFlapBoard extends LitElement {
|
|
|
19
18
|
private _getSpoolEls;
|
|
20
19
|
private _checkAllSettled;
|
|
21
20
|
private _dispatchBoardSettled;
|
|
21
|
+
private _getCurrentGrid;
|
|
22
22
|
render(): TemplateResult;
|
|
23
23
|
}
|
|
24
24
|
declare global {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TSpool } from '../types';
|
|
1
|
+
import type { TBoardData, TLineInput, TSpool } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Fill a uniform 2-D spool grid.
|
|
4
4
|
*
|
|
@@ -19,12 +19,4 @@ export declare function spoolGrid(spool: TSpool | TSpool[], cols: number, rows:
|
|
|
19
19
|
* @param lines - Text lines to display.
|
|
20
20
|
* @param cols - Board width in columns.
|
|
21
21
|
*/
|
|
22
|
-
export declare function fromLines(lines: TLineInput[], cols: number):
|
|
23
|
-
spools: TSpool[][];
|
|
24
|
-
grid: string[][];
|
|
25
|
-
};
|
|
26
|
-
export type TLineInput = string | {
|
|
27
|
-
text: string;
|
|
28
|
-
bg?: string;
|
|
29
|
-
color?: string;
|
|
30
|
-
};
|
|
22
|
+
export declare function fromLines(lines: TLineInput[], cols: number): TBoardData;
|
package/dist/types/lib/flap.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
-
import type { TSpool } from '../types';
|
|
2
|
+
import type { TSplitFlapVariant, TSpool } from '../types';
|
|
3
3
|
import './SplitFlapSpoolMinimal';
|
|
4
4
|
import './SplitFlapSpoolRealistic';
|
|
5
5
|
export declare class SplitFlapSpool extends LitElement {
|
|
6
6
|
static readonly styles: import("lit").CSSResult;
|
|
7
|
-
variant:
|
|
7
|
+
variant: TSplitFlapVariant;
|
|
8
8
|
value: string;
|
|
9
9
|
flaps: TSpool;
|
|
10
10
|
speed: number;
|
|
@@ -16,6 +16,7 @@ export declare class SplitFlapSpool extends LitElement {
|
|
|
16
16
|
/** Returns true when the given key exists in the loaded flaps. */
|
|
17
17
|
hasKey(value: string): boolean;
|
|
18
18
|
private _getActiveSpool;
|
|
19
|
+
private _renderVariant;
|
|
19
20
|
render(): TemplateResult;
|
|
20
21
|
}
|
|
21
22
|
declare global {
|
|
@@ -36,13 +36,16 @@ export declare abstract class SplitFlapSpoolBase extends LitElement {
|
|
|
36
36
|
private _doStep;
|
|
37
37
|
private _scheduleAdvanceOrSettle;
|
|
38
38
|
private _finishSettling;
|
|
39
|
-
private
|
|
39
|
+
private _findFlapIndex;
|
|
40
|
+
private _getNextIndex;
|
|
41
|
+
private _isAnimating;
|
|
42
|
+
private _getRemainingAnimationTime;
|
|
40
43
|
private _resetForEmptyFlaps;
|
|
41
44
|
private _syncIndicesToFlaps;
|
|
42
45
|
private _clearTimers;
|
|
43
|
-
/** Renders
|
|
46
|
+
/** Renders one flap half. Subclass stylesheets must define `.char-inner`, `.color-fill`, `.image-fill`, `.custom-fill`. */
|
|
44
47
|
protected _renderHalf(flap: TFlap, half: 'top' | 'bottom'): TemplateResult;
|
|
45
|
-
/** Returns current and previous flap for
|
|
48
|
+
/** Returns current and previous flap for render. Null when flaps is empty. */
|
|
46
49
|
protected _getFlaps(): {
|
|
47
50
|
current: TFlap;
|
|
48
51
|
prev: TFlap;
|
|
@@ -24,10 +24,10 @@ export declare class SplitFlapSpoolRealistic extends SplitFlapSpoolBase {
|
|
|
24
24
|
disconnectedCallback(): void;
|
|
25
25
|
private _clearWrapTimer;
|
|
26
26
|
private _syncSlotState;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
private _handleFlapChange;
|
|
28
|
+
private _handleIndexChange;
|
|
29
|
+
private _waitForPositionReset;
|
|
30
|
+
private _isWrapForwardStep;
|
|
31
31
|
private _animateStep;
|
|
32
32
|
private _getRenderCenter;
|
|
33
33
|
render(): TemplateResult;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { TemplateResult } from 'lit';
|
|
2
|
+
export type TSplitFlapVariant = 'minimal' | 'realistic';
|
|
2
3
|
export type TSpool = TFlap[];
|
|
4
|
+
export type TGrid = string[][];
|
|
3
5
|
export type TFlap = TFlapChar | TFlapColor | TFlapImage | TFlapCustom;
|
|
4
6
|
export interface TFlapChar {
|
|
5
7
|
type: 'char';
|
|
@@ -28,3 +30,19 @@ export interface TFlapCustom {
|
|
|
28
30
|
top: TemplateResult;
|
|
29
31
|
bottom: TemplateResult;
|
|
30
32
|
}
|
|
33
|
+
export interface TBoardData {
|
|
34
|
+
spools: TSpool[][];
|
|
35
|
+
grid: TGrid;
|
|
36
|
+
}
|
|
37
|
+
export interface TLineConfig {
|
|
38
|
+
text: string;
|
|
39
|
+
bg?: string;
|
|
40
|
+
color?: string;
|
|
41
|
+
}
|
|
42
|
+
export type TLineInput = string | TLineConfig;
|
|
43
|
+
export interface TSpoolSettledDetail {
|
|
44
|
+
value: string;
|
|
45
|
+
}
|
|
46
|
+
export interface TBoardSettledDetail {
|
|
47
|
+
grid: TGrid;
|
|
48
|
+
}
|