split-flap-board 0.0.1 → 0.0.4
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 +452 -1
- package/dist/cjs/SplitFlapBoard.js +61 -0
- package/dist/cjs/index.js +1 -7
- package/dist/cjs/lib/board.js +1 -0
- package/dist/cjs/lib/flap.js +1 -0
- package/dist/cjs/lib/spool-layout.js +1 -0
- package/dist/cjs/spools/SplitFlapSpool.js +18 -0
- package/dist/cjs/spools/SplitFlapSpoolBase.js +16 -0
- package/dist/cjs/spools/SplitFlapSpoolMinimal.js +121 -0
- package/dist/cjs/spools/SplitFlapSpoolRealistic.js +156 -0
- package/dist/cjs/spools/presets.js +1 -0
- package/dist/esm/SplitFlapBoard.js +61 -0
- package/dist/esm/index.js +1 -5
- package/dist/esm/lib/board.js +1 -0
- package/dist/esm/lib/flap.js +1 -0
- package/dist/esm/lib/spool-layout.js +1 -0
- package/dist/esm/spools/SplitFlapSpool.js +18 -0
- package/dist/esm/spools/SplitFlapSpoolBase.js +16 -0
- package/dist/esm/spools/SplitFlapSpoolMinimal.js +121 -0
- package/dist/esm/spools/SplitFlapSpoolRealistic.js +156 -0
- package/dist/esm/spools/presets.js +1 -0
- package/dist/types/SplitFlapBoard.d.ts +28 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/lib/board.d.ts +30 -0
- package/dist/types/lib/flap.d.ts +3 -0
- package/dist/types/lib/index.d.ts +3 -0
- package/dist/types/lib/spool-layout.d.ts +16 -0
- package/dist/types/spools/SplitFlapSpool.d.ts +25 -0
- package/dist/types/spools/SplitFlapSpoolBase.d.ts +52 -0
- package/dist/types/spools/SplitFlapSpoolMinimal.d.ts +11 -0
- package/dist/types/spools/SplitFlapSpoolRealistic.d.ts +39 -0
- package/dist/types/spools/index.d.ts +5 -0
- package/dist/types/spools/presets.d.ts +4 -0
- package/dist/types/types.d.ts +30 -0
- package/package.json +13 -4
- package/dist/cjs/index.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
|
@@ -0,0 +1,156 @@
|
|
|
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 x}from"lit/directives/style-map.js";import"./presets.js";import{getRenderedFlaps as g}from"../lib/spool-layout.js";import{SplitFlapSpoolBase as y}from"./SplitFlapSpoolBase.js";var _=Object.defineProperty,w=Object.getOwnPropertyDescriptor,d=(t,a,e,s)=>{for(var r=s>1?void 0:s?w(a,e):a,i=t.length-1,l;i>=0;i--)(l=t[i])&&(r=(s?l(a,e,r):l(r))||r);return s&&r&&_(a,e,r),r},S=(t,a,e)=>new Promise((s,r)=>{var i=n=>{try{c(e.next(n))}catch(f){r(f)}},l=n=>{try{c(e.throw(n))}catch(f){r(f)}},c=n=>n.done?s(n.value):Promise.resolve(n.value).then(i,l);c((e=e.apply(t,a)).next())});let o=class extends y{constructor(){super(...arguments),this.visibleSideCount=-1,this._slotRef=v(),this._wrapResetTimer=null,this._skipNextIndexAnimation=!1}firstUpdated(){this._syncSlotState()}updated(t){var a;const e=this._currentIndex,s=this._prevIndex;if(super.updated(t),t.has("flaps")){this._clearWrapTimer();const r=this._currentIndex!==e||this._prevIndex!==s;this._skipNextIndexAnimation=r,this._syncSlotState()}if(t.has("_currentIndex")){if(this._skipNextIndexAnimation){this._skipNextIndexAnimation=!1,this._syncSlotState();return}const r=(a=t.get("_currentIndex"))!=null?a:0;this._animateStep(r,this._currentIndex)}}disconnectedCallback(){super.disconnectedCallback(),this._clearWrapTimer()}_clearWrapTimer(){this._wrapResetTimer!=null&&(clearTimeout(this._wrapResetTimer),this._wrapResetTimer=null)}_syncSlotState(){const t=this._slotRef.value;t!=null&&(t.style.setProperty("--current-character-index",String(this._currentIndex)),t.style.setProperty("--_flip-dur","0ms"))}_animateStep(t,a){return S(this,null,function*(){const e=this._slotRef.value;if(e==null)return;this._clearWrapTimer(),e.style.setProperty("--_flip-dur","0ms"),e.style.setProperty("--current-character-index",String(t)),yield new Promise(i=>requestAnimationFrame(()=>{requestAnimationFrame(()=>i())}));const s=t===this.flaps.length-1&&a===0,r=s?this.flaps.length:a;e.style.setProperty("--_flip-dur",`${this._animDur}ms`),e.style.setProperty("--current-character-index",String(r)),s&&(this._wrapResetTimer=setTimeout(()=>{e.style.setProperty("--_flip-dur","0ms"),e.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 t=this._getRenderCenter(),a=g(this.flaps,this._currentIndex,{visibleSideCount:this.visibleSideCount,renderCenter:t});return p`
|
|
2
|
+
<div
|
|
3
|
+
${b(this._slotRef)}
|
|
4
|
+
class="slot"
|
|
5
|
+
style=${x({"--total":String(this.flaps.length)})}
|
|
6
|
+
>
|
|
7
|
+
${a.map(({flap:e,actualIndex:s,renderedIndex:r})=>{const i=this.flaps.length>2&&this.flaps.length%2===0&&Math.abs(r-t)===this.flaps.length/2;return p`
|
|
8
|
+
<div
|
|
9
|
+
class="character ${i?"is-background":""}"
|
|
10
|
+
style="--index: ${r}"
|
|
11
|
+
data-index=${s}
|
|
12
|
+
>
|
|
13
|
+
<div class="flap">${this._renderHalf(e,"top")}</div>
|
|
14
|
+
<div class="flap" aria-hidden="true">${this._renderHalf(e,"bottom")}</div>
|
|
15
|
+
</div>
|
|
16
|
+
`})}
|
|
17
|
+
</div>
|
|
18
|
+
`}};o.styles=h`
|
|
19
|
+
:host {
|
|
20
|
+
display: inline-block;
|
|
21
|
+
perspective: var(--sfb-perspective, 400px);
|
|
22
|
+
font-size: 2rem;
|
|
23
|
+
font-family: monospace;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.slot {
|
|
27
|
+
display: grid;
|
|
28
|
+
place-content: center;
|
|
29
|
+
/* Keep board tilt on the inner slot so the drum stays inside host perspective. */
|
|
30
|
+
transform: var(--sfb-view-transform, none);
|
|
31
|
+
transform-style: preserve-3d;
|
|
32
|
+
transition: transform 0.5s cubic-bezier(0.25, 0, 0.3, 1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Derive drum placement and fold angles from the flap offset. */
|
|
36
|
+
.character {
|
|
37
|
+
--offset: calc(var(--index) - var(--current-character-index));
|
|
38
|
+
--abs-offset: max(var(--offset), calc(var(--offset) * -1));
|
|
39
|
+
--safe-abs-offset: max(var(--abs-offset), 0.001);
|
|
40
|
+
--direction: calc(var(--offset) / var(--safe-abs-offset));
|
|
41
|
+
--past: min(0, var(--direction)); /* -1 when behind current, else 0 */
|
|
42
|
+
--future: max(0, var(--direction)); /* +1 when ahead of current, else 0 */
|
|
43
|
+
/* Treat exact offsets as booleans so CSS can target the active neighbors. */
|
|
44
|
+
--is-current: clamp(0, calc(1 - var(--abs-offset) * 1000), 1);
|
|
45
|
+
--is-previous: clamp(0, calc(1 - max(var(--offset) + 1, (var(--offset) + 1) * -1) * 1000), 1);
|
|
46
|
+
--is-next: clamp(0, calc(1 - max(var(--offset) - 1, (var(--offset) - 1) * -1) * 1000), 1);
|
|
47
|
+
/*
|
|
48
|
+
* Keep the outer visible odd-count flap inside the silhouette so it does not
|
|
49
|
+
* vanish at the drum edge.
|
|
50
|
+
*/
|
|
51
|
+
--natural-angle: calc((0.5 / var(--total)) * 1turn);
|
|
52
|
+
/* Cap the visual step angle for small spools so the gap between flaps stays tight. */
|
|
53
|
+
--angle: min(var(--natural-angle), var(--sfb-max-step-angle, 1turn));
|
|
54
|
+
/* Unwrapped drum position; places the fold hinge on the cylinder surface. */
|
|
55
|
+
--drum-a: calc(var(--abs-offset) * var(--direction) * var(--angle));
|
|
56
|
+
/* Top-half angle; +0.5turn for past flaps puts them on the drum backface. */
|
|
57
|
+
--a: calc(var(--abs-offset) * var(--direction) * var(--angle) + var(--past) * 0.5turn);
|
|
58
|
+
/* Bottom-half angle; +0.5turn for future flaps slides them in from below. */
|
|
59
|
+
--a2: calc(
|
|
60
|
+
max(var(--abs-offset) - 1, 0) * var(--direction) * var(--angle) + var(--future) * 0.5turn
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
display: flex;
|
|
64
|
+
grid-area: 1 / 1;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: var(--sfb-crease, 1px);
|
|
67
|
+
/* Move the fold line onto the drum surface so translation and fold stay aligned. */
|
|
68
|
+
transform: translateZ(calc(var(--sfb-drum-radius, 0px) * cos(var(--drum-a))))
|
|
69
|
+
translateY(calc(var(--sfb-drum-radius, 0px) * sin(var(--drum-a)) * -1));
|
|
70
|
+
transform-style: preserve-3d;
|
|
71
|
+
z-index: calc(var(--is-current) * 2 + var(--is-previous) + var(--is-next));
|
|
72
|
+
transition: transform var(--_flip-dur, 0ms) cubic-bezier(0.25, 0, 0.5, 1);
|
|
73
|
+
pointer-events: none;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.character.is-background {
|
|
77
|
+
opacity: 0;
|
|
78
|
+
z-index: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.flap {
|
|
82
|
+
position: relative;
|
|
83
|
+
transform-style: preserve-3d;
|
|
84
|
+
backface-visibility: hidden;
|
|
85
|
+
transition: transform var(--_flip-dur, 0ms) cubic-bezier(0.25, 0, 0.5, 1);
|
|
86
|
+
will-change: transform;
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
box-shadow:
|
|
89
|
+
0 2px 6px rgba(0, 0, 0, 0.7),
|
|
90
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
91
|
+
border: 1px solid var(--sfb-flap-border, #2a2a2a);
|
|
92
|
+
border-radius: var(--sfb-flap-radius, 5px);
|
|
93
|
+
background: var(--sfb-flap-bg, #111);
|
|
94
|
+
width: var(--sfb-spool-width, 1em);
|
|
95
|
+
height: calc(var(--sfb-spool-height, 2em) / 2);
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
color: var(--sfb-flap-color, #f5f0e0);
|
|
98
|
+
line-height: 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Nudge the front flap forward so the center seam stays stable during the flip. */
|
|
102
|
+
.flap:first-child {
|
|
103
|
+
transform: translateZ(calc(var(--is-current) * 0.1px)) rotateX(var(--a));
|
|
104
|
+
transform-origin: center calc(100% + var(--sfb-crease, 1px) * 0.5);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.flap:last-child {
|
|
108
|
+
transform: translateZ(calc(var(--is-current) * 0.1px)) rotateX(var(--a2));
|
|
109
|
+
transform-origin: center calc(var(--sfb-crease, 1px) * -0.5);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Use 200% fills so each half crops the same flap content at the fold. */
|
|
113
|
+
.char-inner,
|
|
114
|
+
.image-fill,
|
|
115
|
+
.custom-fill {
|
|
116
|
+
position: absolute;
|
|
117
|
+
left: 0;
|
|
118
|
+
width: 100%;
|
|
119
|
+
height: 200%;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.flap:first-child .char-inner,
|
|
123
|
+
.flap:first-child .image-fill,
|
|
124
|
+
.flap:first-child .custom-fill {
|
|
125
|
+
top: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.flap:last-child .char-inner,
|
|
129
|
+
.flap:last-child .image-fill,
|
|
130
|
+
.flap:last-child .custom-fill {
|
|
131
|
+
bottom: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.char-inner {
|
|
135
|
+
display: flex;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
align-items: center;
|
|
138
|
+
font-weight: bold;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.color-fill {
|
|
142
|
+
position: absolute;
|
|
143
|
+
inset: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.image-fill {
|
|
147
|
+
object-fit: cover;
|
|
148
|
+
object-position: center center;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.custom-fill {
|
|
152
|
+
display: flex;
|
|
153
|
+
justify-content: center;
|
|
154
|
+
align-items: center;
|
|
155
|
+
}
|
|
156
|
+
`,d([u({type:Number})],o.prototype,"visibleSideCount",2),o=d([m("split-flap-spool-realistic")],o);export{o as SplitFlapSpoolRealistic};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const l=[{type:"char",value:" "},..."ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map(e=>({type:"char",value:e})),..."0123456789".split("").map(e=>({type:"char",value:e})),{type:"char",value:"."},{type:"char",value:"-"},{type:"char",value:"/"},{type:"char",value:":"}],a=[{type:"char",value:" "},..."0123456789".split("").map(e=>({type:"char",value:e}))],p=[{type:"color",key:"blank",value:"#111111"},{type:"color",key:"red",value:"#ef4444"},{type:"color",key:"orange",value:"#f97316"},{type:"color",key:"yellow",value:"#eab308"},{type:"color",key:"green",value:"#22c55e"},{type:"color",key:"blue",value:"#3b82f6"},{type:"color",key:"purple",value:"#8b5cf6"},{type:"color",key:"pink",value:"#ec4899"}];export{l as charSpool,p as colorSpool,a as numericSpool};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
+
import './spools/SplitFlapSpool';
|
|
3
|
+
import type { TSpool } from './types';
|
|
4
|
+
export declare class SplitFlapBoard extends LitElement {
|
|
5
|
+
static readonly styles: import("lit").CSSResult;
|
|
6
|
+
/** 2-D grid of spool configs; defines what each position can show. Row-major order. */
|
|
7
|
+
spools: TSpool[][];
|
|
8
|
+
/** 2-D grid of target keys; defines what each position currently shows. Row-major order. */
|
|
9
|
+
grid: string[][];
|
|
10
|
+
/** Flip speed in ms, forwarded to every child spool. */
|
|
11
|
+
speed: number;
|
|
12
|
+
/** Visual variant forwarded to every child spool. */
|
|
13
|
+
variant: 'minimal' | 'realistic';
|
|
14
|
+
/** Number of visible drum sides forwarded to every child spool (-1 = default). */
|
|
15
|
+
visibleSideCount: number;
|
|
16
|
+
/** True while at least one spool is still animating toward its target. */
|
|
17
|
+
private _pendingSettle;
|
|
18
|
+
updated(changed: Map<string, unknown>): void;
|
|
19
|
+
private _getSpoolEls;
|
|
20
|
+
private _checkAllSettled;
|
|
21
|
+
private _dispatchBoardSettled;
|
|
22
|
+
render(): TemplateResult;
|
|
23
|
+
}
|
|
24
|
+
declare global {
|
|
25
|
+
interface HTMLElementTagNameMap {
|
|
26
|
+
'split-flap-board': SplitFlapBoard;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TSpool } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Fill a uniform 2-D spool grid.
|
|
4
|
+
*
|
|
5
|
+
* Pass a single `TSpool` to use the same flap set for every cell, or a `TSpool[]`
|
|
6
|
+
* (one spool per column, cycled) to vary the flap set by column.
|
|
7
|
+
*
|
|
8
|
+
* @param spool - Single spool or one spool per column.
|
|
9
|
+
* @param cols - Number of columns.
|
|
10
|
+
* @param rows - Number of rows.
|
|
11
|
+
*/
|
|
12
|
+
export declare function spoolGrid(spool: TSpool | TSpool[], cols: number, rows: number): TSpool[][];
|
|
13
|
+
/**
|
|
14
|
+
* Convert lines of text into a `{ spools, grid }` pair ready for `<split-flap-board>`.
|
|
15
|
+
*
|
|
16
|
+
* Each line can be a plain string or `{ text, bg?, color? }`. Text is uppercased
|
|
17
|
+
* and padded/truncated to `cols` characters to match the default `charSpool` key set.
|
|
18
|
+
*
|
|
19
|
+
* @param lines - Text lines to display.
|
|
20
|
+
* @param cols - Board width in columns.
|
|
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
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TFlap } from '../types';
|
|
2
|
+
/** One flap instance prepared for rendering around the realistic drum. */
|
|
3
|
+
export interface TRenderedFlap {
|
|
4
|
+
flap: TFlap;
|
|
5
|
+
actualIndex: number;
|
|
6
|
+
renderedIndex: number;
|
|
7
|
+
}
|
|
8
|
+
/** Returns the shortest signed distance between two indices on a circular spool. */
|
|
9
|
+
export declare function getSignedCircularOffset(index: number, currentIndex: number, total: number): number;
|
|
10
|
+
/** Clamps visible side count to the maximum non-overlapping half of the drum. */
|
|
11
|
+
export declare function getMaxVisibleSideCount(total: number, visibleSideCount?: number): number;
|
|
12
|
+
/** Returns the subset of flaps that should be rendered around the current center. */
|
|
13
|
+
export declare function getRenderedFlaps(flaps: TFlap[], currentIndex: number, { visibleSideCount, renderCenter }?: {
|
|
14
|
+
visibleSideCount?: number;
|
|
15
|
+
renderCenter?: number;
|
|
16
|
+
}): TRenderedFlap[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
+
import type { TSpool } from '../types';
|
|
3
|
+
import './SplitFlapSpoolMinimal';
|
|
4
|
+
import './SplitFlapSpoolRealistic';
|
|
5
|
+
export declare class SplitFlapSpool extends LitElement {
|
|
6
|
+
static readonly styles: import("lit").CSSResult;
|
|
7
|
+
variant: 'minimal' | 'realistic';
|
|
8
|
+
value: string;
|
|
9
|
+
flaps: TSpool;
|
|
10
|
+
speed: number;
|
|
11
|
+
visibleSideCount: number;
|
|
12
|
+
/** Key currently shown by the active variant; falls back to the first flap key before the variant mounts. */
|
|
13
|
+
get currentValue(): string | undefined;
|
|
14
|
+
/** True when the active variant is idle with no pending target. */
|
|
15
|
+
get isSettled(): boolean;
|
|
16
|
+
/** Returns true when the given key exists in the loaded flaps. */
|
|
17
|
+
hasKey(value: string): boolean;
|
|
18
|
+
private _getActiveSpool;
|
|
19
|
+
render(): TemplateResult;
|
|
20
|
+
}
|
|
21
|
+
declare global {
|
|
22
|
+
interface HTMLElementTagNameMap {
|
|
23
|
+
'split-flap-spool': SplitFlapSpool;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LitElement, nothing, type TemplateResult } from 'lit';
|
|
2
|
+
import type { TFlap, TSpool } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for all spool variants. Contains stepping logic and HTML helpers,
|
|
5
|
+
* but leaves styling and element registration to subclasses.
|
|
6
|
+
*
|
|
7
|
+
* Extend this to build a custom spool variant:
|
|
8
|
+
* export class MySpool extends SplitFlapSpoolBase {
|
|
9
|
+
* static readonly styles = css`...`;
|
|
10
|
+
* override render() { ... }
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
export declare abstract class SplitFlapSpoolBase extends LitElement {
|
|
14
|
+
value: string;
|
|
15
|
+
flaps: TSpool;
|
|
16
|
+
speed: number;
|
|
17
|
+
protected _currentIndex: number;
|
|
18
|
+
protected _prevIndex: number;
|
|
19
|
+
protected _stepping: boolean;
|
|
20
|
+
private _targetIndex;
|
|
21
|
+
private _stepTimer;
|
|
22
|
+
private _animTimer;
|
|
23
|
+
private _animEndsAt;
|
|
24
|
+
protected get _animDur(): number;
|
|
25
|
+
updated(changed: Map<string, unknown>): void;
|
|
26
|
+
disconnectedCallback(): void;
|
|
27
|
+
/** The flap currently shown; falls back to index 0 when out of range. */
|
|
28
|
+
get currentFlap(): TFlap | undefined;
|
|
29
|
+
/** Key of the currently shown flap, or undefined when flaps is empty. */
|
|
30
|
+
get currentValue(): string | undefined;
|
|
31
|
+
/** True when the spool is idle: no animation running and no pending target. */
|
|
32
|
+
get isSettled(): boolean;
|
|
33
|
+
/** Returns true when the given key exists in the currently loaded flaps. */
|
|
34
|
+
hasKey(value: string): boolean;
|
|
35
|
+
private _startStepping;
|
|
36
|
+
private _doStep;
|
|
37
|
+
private _scheduleAdvanceOrSettle;
|
|
38
|
+
private _finishSettling;
|
|
39
|
+
private _getRemainingAnimTime;
|
|
40
|
+
private _resetForEmptyFlaps;
|
|
41
|
+
private _syncIndicesToFlaps;
|
|
42
|
+
private _clearTimers;
|
|
43
|
+
/** Renders the content of one flap half. Expects `.char-inner`, `.color-fill`, `.image-fill`, `.custom-fill` class names in the subclass stylesheet. */
|
|
44
|
+
protected _renderHalf(flap: TFlap, half: 'top' | 'bottom'): TemplateResult;
|
|
45
|
+
/** Returns current and previous flap for use in render. Returns null when flaps is empty. */
|
|
46
|
+
protected _getFlaps(): {
|
|
47
|
+
current: TFlap;
|
|
48
|
+
prev: TFlap;
|
|
49
|
+
} | null;
|
|
50
|
+
/** Renders the two card halves. Wrap this in a `.spool` with `--_anim-dur` set. */
|
|
51
|
+
protected _renderCard(): TemplateResult | typeof nothing;
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { SplitFlapSpoolBase } from './SplitFlapSpoolBase';
|
|
3
|
+
export declare class SplitFlapSpoolMinimal extends SplitFlapSpoolBase {
|
|
4
|
+
static readonly styles: import("lit").CSSResult;
|
|
5
|
+
render(): TemplateResult;
|
|
6
|
+
}
|
|
7
|
+
declare global {
|
|
8
|
+
interface HTMLElementTagNameMap {
|
|
9
|
+
'split-flap-spool-minimal': SplitFlapSpoolMinimal;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { SplitFlapSpoolBase } from './SplitFlapSpoolBase';
|
|
3
|
+
/**
|
|
4
|
+
* Realistic split-flap spool using a 3D drum.
|
|
5
|
+
*
|
|
6
|
+
* Each flap keeps its own top and bottom half, while the hinge is positioned
|
|
7
|
+
* around a cylinder so the drum reads in perspective when the view is tilted.
|
|
8
|
+
*
|
|
9
|
+
* CSS approach adapted from Emil Kowalski's split-flap implementation.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SplitFlapSpoolRealistic extends SplitFlapSpoolBase {
|
|
12
|
+
/**
|
|
13
|
+
* Limits how many flaps are rendered on each side of the drum.
|
|
14
|
+
* Negative values use the full visible half. This property is ignored by
|
|
15
|
+
* the `minimal` variant because it only renders the active card.
|
|
16
|
+
*/
|
|
17
|
+
visibleSideCount: number;
|
|
18
|
+
private readonly _slotRef;
|
|
19
|
+
private _wrapResetTimer;
|
|
20
|
+
private _skipNextIndexAnimation;
|
|
21
|
+
static readonly styles: import("lit").CSSResult;
|
|
22
|
+
firstUpdated(): void;
|
|
23
|
+
updated(changed: Map<string, unknown>): void;
|
|
24
|
+
disconnectedCallback(): void;
|
|
25
|
+
private _clearWrapTimer;
|
|
26
|
+
private _syncSlotState;
|
|
27
|
+
/**
|
|
28
|
+
* Snap back to prevIdx (instant), then transition to nextIdx.
|
|
29
|
+
* For forward-only adjacent steps the drum advances by exactly one --angle.
|
|
30
|
+
*/
|
|
31
|
+
private _animateStep;
|
|
32
|
+
private _getRenderCenter;
|
|
33
|
+
render(): TemplateResult;
|
|
34
|
+
}
|
|
35
|
+
declare global {
|
|
36
|
+
interface HTMLElementTagNameMap {
|
|
37
|
+
'split-flap-spool-realistic': SplitFlapSpoolRealistic;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TemplateResult } from 'lit';
|
|
2
|
+
export type TSpool = TFlap[];
|
|
3
|
+
export type TFlap = TFlapChar | TFlapColor | TFlapImage | TFlapCustom;
|
|
4
|
+
export interface TFlapChar {
|
|
5
|
+
type: 'char';
|
|
6
|
+
key?: string;
|
|
7
|
+
value: string;
|
|
8
|
+
color?: string;
|
|
9
|
+
bg?: string;
|
|
10
|
+
fontSize?: string;
|
|
11
|
+
fontFamily?: string;
|
|
12
|
+
fontWeight?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TFlapColor {
|
|
15
|
+
type: 'color';
|
|
16
|
+
key?: string;
|
|
17
|
+
value: string;
|
|
18
|
+
}
|
|
19
|
+
export interface TFlapImage {
|
|
20
|
+
type: 'image';
|
|
21
|
+
key?: string;
|
|
22
|
+
src: string;
|
|
23
|
+
alt?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface TFlapCustom {
|
|
26
|
+
type: 'custom';
|
|
27
|
+
key: string;
|
|
28
|
+
top: TemplateResult;
|
|
29
|
+
bottom: TemplateResult;
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "split-flap-board",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "",
|
|
6
|
-
"keywords": [
|
|
5
|
+
"description": "Web component that simulates a split-flap display inspired by airport and train station boards",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"split-flap",
|
|
8
|
+
"solari-board",
|
|
9
|
+
"web-component",
|
|
10
|
+
"lit",
|
|
11
|
+
"display-board"
|
|
12
|
+
],
|
|
7
13
|
"homepage": "https://builder.group/?utm_source=package-json",
|
|
8
14
|
"bugs": {
|
|
9
15
|
"url": "https://github.com/builder-group/community/issues"
|
|
@@ -22,6 +28,9 @@
|
|
|
22
28
|
"dist",
|
|
23
29
|
"README.md"
|
|
24
30
|
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"lit": "^3.3.0"
|
|
33
|
+
},
|
|
25
34
|
"devDependencies": {
|
|
26
35
|
"@types/node": "^25.2.3",
|
|
27
36
|
"@blgc/config": "0.0.40",
|
|
@@ -40,7 +49,7 @@
|
|
|
40
49
|
"lint": "eslint . --fix",
|
|
41
50
|
"publish:patch": "pnpm build:prod && pnpm version patch && pnpm publish --no-git-checks --access=public",
|
|
42
51
|
"size": "size-limit --why",
|
|
43
|
-
"start:dev": "
|
|
52
|
+
"start:dev": "vite --config ./dev/vite.config.mjs",
|
|
44
53
|
"test": "vitest run",
|
|
45
54
|
"update:latest": "pnpm update --latest"
|
|
46
55
|
}
|
package/dist/cjs/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["export function helloWorld() {}\n"],"names":[],"mappings":";;AACO,SAAS,UAAU,GAAG;AAC7B;;;;"}
|
package/dist/esm/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["export function helloWorld() {}\n"],"names":[],"mappings":"AACO,SAAS,UAAU,GAAG;AAC7B;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,SAAK"}
|