@anyknown/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/components/action-bar/ActionBar.d.ts +28 -0
- package/dist/components/action-bar/ActionBar.js +73 -0
- package/dist/components/badge/Badge.d.ts +28 -0
- package/dist/components/badge/Badge.js +79 -0
- package/dist/components/button/Button.d.ts +7 -0
- package/dist/components/button/Button.js +86 -0
- package/dist/components/card/Card.d.ts +2 -0
- package/dist/components/card/Card.js +17 -0
- package/dist/components/checkbox/Checkbox.d.ts +8 -0
- package/dist/components/checkbox/Checkbox.js +102 -0
- package/dist/components/code-block/CodeBlock.d.ts +10 -0
- package/dist/components/code-block/CodeBlock.js +103 -0
- package/dist/components/composer/Composer.d.ts +27 -0
- package/dist/components/composer/Composer.js +278 -0
- package/dist/components/data-table/DataTable.d.ts +38 -0
- package/dist/components/data-table/DataTable.js +256 -0
- package/dist/components/dialog/Dialog.d.ts +36 -0
- package/dist/components/dialog/Dialog.js +77 -0
- package/dist/components/diff-viewer/DiffViewer.d.ts +13 -0
- package/dist/components/diff-viewer/DiffViewer.js +133 -0
- package/dist/components/dropdown/DropdownMenu.d.ts +43 -0
- package/dist/components/dropdown/DropdownMenu.js +86 -0
- package/dist/components/dropzone/Dropzone.d.ts +32 -0
- package/dist/components/dropzone/Dropzone.js +242 -0
- package/dist/components/empty-state/EmptyState.d.ts +9 -0
- package/dist/components/empty-state/EmptyState.js +41 -0
- package/dist/components/file-row/FileRow.d.ts +32 -0
- package/dist/components/file-row/FileRow.js +153 -0
- package/dist/components/handoff-receipt/HandoffReceipt.d.ts +15 -0
- package/dist/components/handoff-receipt/HandoffReceipt.js +147 -0
- package/dist/components/input/Input.d.ts +48 -0
- package/dist/components/input/Input.js +64 -0
- package/dist/components/interaction-card/InteractionCard.d.ts +59 -0
- package/dist/components/interaction-card/InteractionCard.js +238 -0
- package/dist/components/kbd/Kbd.d.ts +9 -0
- package/dist/components/kbd/Kbd.js +43 -0
- package/dist/components/label/Field.d.ts +11 -0
- package/dist/components/label/Field.js +28 -0
- package/dist/components/label/Label.d.ts +7 -0
- package/dist/components/label/Label.js +21 -0
- package/dist/components/label/fieldContext.d.ts +26 -0
- package/dist/components/label/fieldContext.js +16 -0
- package/dist/components/message/Message.d.ts +29 -0
- package/dist/components/message/Message.js +94 -0
- package/dist/components/password-input/PasswordInput.d.ts +20 -0
- package/dist/components/password-input/PasswordInput.js +141 -0
- package/dist/components/popover/Popover.d.ts +38 -0
- package/dist/components/popover/Popover.js +37 -0
- package/dist/components/progress/Progress.d.ts +36 -0
- package/dist/components/progress/Progress.js +200 -0
- package/dist/components/radio/Radio.d.ts +7 -0
- package/dist/components/radio/Radio.js +89 -0
- package/dist/components/radio/RadioGroup.d.ts +21 -0
- package/dist/components/radio/RadioGroup.js +34 -0
- package/dist/components/reasoning-fold/ReasoningFold.d.ts +10 -0
- package/dist/components/reasoning-fold/ReasoningFold.js +92 -0
- package/dist/components/recovery-key/RecoveryKey.d.ts +17 -0
- package/dist/components/recovery-key/RecoveryKey.js +132 -0
- package/dist/components/select/Select.d.ts +31 -0
- package/dist/components/select/Select.js +209 -0
- package/dist/components/skeleton/Skeleton.d.ts +20 -0
- package/dist/components/skeleton/Skeleton.js +57 -0
- package/dist/components/switch/Switch.d.ts +7 -0
- package/dist/components/switch/Switch.js +108 -0
- package/dist/components/tabs/Tabs.d.ts +28 -0
- package/dist/components/tabs/Tabs.js +133 -0
- package/dist/components/text/Text.d.ts +7 -0
- package/dist/components/text/Text.js +31 -0
- package/dist/components/textarea/Textarea.d.ts +7 -0
- package/dist/components/textarea/Textarea.js +25 -0
- package/dist/components/toast/Toast.d.ts +56 -0
- package/dist/components/toast/Toast.js +175 -0
- package/dist/components/tool-card/ToolCard.d.ts +55 -0
- package/dist/components/tool-card/ToolCard.js +281 -0
- package/dist/components/tooltip/Tooltip.d.ts +12 -0
- package/dist/components/tooltip/Tooltip.js +38 -0
- package/dist/components/voice-indicator/VoiceIndicator.d.ts +7 -0
- package/dist/components/voice-indicator/VoiceIndicator.js +51 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +38 -0
- package/dist/lib/diff.d.ts +28 -0
- package/dist/lib/diff.js +133 -0
- package/dist/lib/format.d.ts +2 -0
- package/dist/lib/format.js +24 -0
- package/dist/lib/format.test.d.ts +1 -0
- package/dist/lib/format.test.js +37 -0
- package/dist/lib/mergeRefs.d.ts +2 -0
- package/dist/lib/mergeRefs.js +8 -0
- package/dist/lib/motion.d.ts +1 -0
- package/dist/lib/motion.js +10 -0
- package/dist/lib/paths.d.ts +3 -0
- package/dist/lib/paths.js +11 -0
- package/dist/lib/popup.d.ts +29 -0
- package/dist/lib/popup.js +27 -0
- package/dist/lib/progress.d.ts +2 -0
- package/dist/lib/progress.js +28 -0
- package/dist/lib/silk.d.ts +66 -0
- package/dist/lib/silk.js +337 -0
- package/dist/lib/styled.d.ts +29 -0
- package/dist/lib/styled.js +30 -0
- package/dist/lib/svgId.d.ts +1 -0
- package/dist/lib/svgId.js +4 -0
- package/dist/lib/useAnimationFrame.d.ts +1 -0
- package/dist/lib/useAnimationFrame.js +21 -0
- package/dist/lib/useControllableState.d.ts +1 -0
- package/dist/lib/useControllableState.js +14 -0
- package/dist/lib/useCopy.d.ts +4 -0
- package/dist/lib/useCopy.js +19 -0
- package/dist/lib/voice.d.ts +2 -0
- package/dist/lib/voice.js +30 -0
- package/dist/lib/weave.d.ts +28 -0
- package/dist/lib/weave.js +83 -0
- package/dist/scrollbar.css +29 -0
- package/dist/themes.stylex.d.ts +49 -0
- package/dist/themes.stylex.js +52 -0
- package/dist/tokens.css +109 -0
- package/dist/tokens.stylex.d.ts +126 -0
- package/dist/tokens.stylex.js +149 -0
- package/package.json +81 -0
package/dist/lib/silk.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// 織體動態引擎(TEXTURE-GUIDE §3–4):lib/weave.ts 的動態孿生。
|
|
2
|
+
// weave.ts 給固定尺寸小控件在 module scope 預織;這裡給尺寸不定、要回應觸點的面
|
|
3
|
+
// (button 全套:帶動 + 窩 + 掃光;卡面「僅展示」子集:hover 帶動 + 光澤帶)。
|
|
4
|
+
// 幾何公式與 weave.ts 完全同源(mulberry32(10075)、共享波場、四層紗)。
|
|
5
|
+
// client-only:在 ref callback 裡 new、cleanup 裡 destroy()(useEffect 禁用,
|
|
6
|
+
// 見 Button.tsx 的用法)。
|
|
7
|
+
import { WEAVE_SEED } from "./weave.js";
|
|
8
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
9
|
+
let uid = 0;
|
|
10
|
+
function mulberry32(seed) {
|
|
11
|
+
let a = seed >>> 0;
|
|
12
|
+
return () => {
|
|
13
|
+
a |= 0;
|
|
14
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
15
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
16
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
17
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function el(name, attrs, parent) {
|
|
21
|
+
const node = document.createElementNS(NS, name);
|
|
22
|
+
for (const [k, v] of Object.entries(attrs))
|
|
23
|
+
node.setAttribute(k, String(v));
|
|
24
|
+
parent?.append(node);
|
|
25
|
+
return node;
|
|
26
|
+
}
|
|
27
|
+
const STEP = 4;
|
|
28
|
+
export class SilkBody {
|
|
29
|
+
host;
|
|
30
|
+
svg;
|
|
31
|
+
opts;
|
|
32
|
+
reduced;
|
|
33
|
+
id;
|
|
34
|
+
w = 0;
|
|
35
|
+
h = 0;
|
|
36
|
+
built = false;
|
|
37
|
+
live = [];
|
|
38
|
+
sheen = [];
|
|
39
|
+
bandG;
|
|
40
|
+
sheenG;
|
|
41
|
+
pointer = null;
|
|
42
|
+
pressed = false;
|
|
43
|
+
gx = 0;
|
|
44
|
+
gy = 0;
|
|
45
|
+
tension = 0;
|
|
46
|
+
tenV = 0;
|
|
47
|
+
pressX = 0;
|
|
48
|
+
pressY = 0;
|
|
49
|
+
bx = 0;
|
|
50
|
+
bandOp = 0;
|
|
51
|
+
sweep = null;
|
|
52
|
+
raf = 0;
|
|
53
|
+
ro;
|
|
54
|
+
rebuild = 0;
|
|
55
|
+
removers = [];
|
|
56
|
+
constructor(host, svg, options) {
|
|
57
|
+
this.host = host;
|
|
58
|
+
this.svg = svg;
|
|
59
|
+
this.opts = {
|
|
60
|
+
bandMax: 0.75,
|
|
61
|
+
pitch: options.ghost ? 2.1 : 1.2,
|
|
62
|
+
hiCount: options.ghost ? 2 : 5,
|
|
63
|
+
hiSpan: 0.6,
|
|
64
|
+
...options,
|
|
65
|
+
};
|
|
66
|
+
this.reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
67
|
+
this.id = `silk${uid++}`;
|
|
68
|
+
this.build();
|
|
69
|
+
this.ro = new ResizeObserver(() => {
|
|
70
|
+
clearTimeout(this.rebuild);
|
|
71
|
+
this.rebuild = window.setTimeout(() => this.build(), this.opts.fixedHeight ? 120 : 0);
|
|
72
|
+
});
|
|
73
|
+
this.ro.observe(host);
|
|
74
|
+
const on = (target, type, fn) => {
|
|
75
|
+
target.addEventListener(type, fn);
|
|
76
|
+
this.removers.push(() => target.removeEventListener(type, fn));
|
|
77
|
+
};
|
|
78
|
+
on(host, "pointerenter", (e) => this.track(e, true));
|
|
79
|
+
on(host, "pointermove", (e) => this.track(e));
|
|
80
|
+
on(host, "pointerleave", () => {
|
|
81
|
+
this.pointer = null;
|
|
82
|
+
this.wake();
|
|
83
|
+
});
|
|
84
|
+
if (this.opts.mode === "press") {
|
|
85
|
+
on(host, "pointerdown", (e) => {
|
|
86
|
+
if (this.disabled())
|
|
87
|
+
return;
|
|
88
|
+
this.pressed = true;
|
|
89
|
+
this.track(e);
|
|
90
|
+
this.pressX = this.gx;
|
|
91
|
+
this.pressY = this.gy;
|
|
92
|
+
this.wake();
|
|
93
|
+
});
|
|
94
|
+
on(window, "pointerup", () => this.release());
|
|
95
|
+
on(host, "keydown", (e) => {
|
|
96
|
+
if ((e.key === " " || e.key === "Enter") && !this.disabled()) {
|
|
97
|
+
this.pointer = { x: this.w / 2, y: this.h / 2, vx: 0, vy: 0, speed: 0 };
|
|
98
|
+
this.gx = this.bx = this.pressX = this.w / 2;
|
|
99
|
+
this.gy = this.pressY = this.h / 2;
|
|
100
|
+
this.pressed = true;
|
|
101
|
+
this.wake();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
on(host, "keyup", () => {
|
|
105
|
+
this.release();
|
|
106
|
+
this.pointer = null;
|
|
107
|
+
});
|
|
108
|
+
on(host, "blur", () => {
|
|
109
|
+
this.pointer = null;
|
|
110
|
+
this.wake();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
destroy() {
|
|
115
|
+
this.ro.disconnect();
|
|
116
|
+
clearTimeout(this.rebuild);
|
|
117
|
+
for (const remove of this.removers)
|
|
118
|
+
remove();
|
|
119
|
+
if (this.raf)
|
|
120
|
+
cancelAnimationFrame(this.raf);
|
|
121
|
+
this.raf = 0;
|
|
122
|
+
}
|
|
123
|
+
disabled() {
|
|
124
|
+
return this.host.matches(":disabled, [aria-disabled=true]");
|
|
125
|
+
}
|
|
126
|
+
build() {
|
|
127
|
+
const w = this.host.clientWidth;
|
|
128
|
+
const h = this.opts.fixedHeight ?? this.host.clientHeight;
|
|
129
|
+
if (w === 0 || (this.built && w === this.w && h === this.h))
|
|
130
|
+
return;
|
|
131
|
+
this.w = w;
|
|
132
|
+
this.h = h;
|
|
133
|
+
this.built = true;
|
|
134
|
+
const rng = mulberry32(WEAVE_SEED);
|
|
135
|
+
const rand = (a, b) => a + rng() * (b - a);
|
|
136
|
+
const W1 = rand(Math.max(120, w * 0.9), w * 1.8);
|
|
137
|
+
const A1 = rand(1.0, 1.8);
|
|
138
|
+
const P1 = rand(0, 6.28);
|
|
139
|
+
const K1 = rand(0.05, 0.11);
|
|
140
|
+
const W2 = rand(w * 1.6, w * 3);
|
|
141
|
+
const A2 = rand(0.5, 0.9);
|
|
142
|
+
const P2 = rand(0, 6.28);
|
|
143
|
+
const K2 = rand(0.03, 0.07);
|
|
144
|
+
const field = (x, y) => A1 * Math.sin((x / W1) * 6.283 + P1 + y * K1) + A2 * Math.sin((x / W2) * 6.283 + P2 + y * K2);
|
|
145
|
+
const staticD = (y0) => {
|
|
146
|
+
let d = "";
|
|
147
|
+
for (let x = -4; x <= w + 4; x += STEP)
|
|
148
|
+
d += `${d ? "L" : "M"}${x},${(y0 + field(x, y0)).toFixed(2)}`;
|
|
149
|
+
return d;
|
|
150
|
+
};
|
|
151
|
+
const p = this.opts.palette;
|
|
152
|
+
this.svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
|
153
|
+
if (this.opts.fixedHeight) {
|
|
154
|
+
this.svg.setAttribute("width", String(w));
|
|
155
|
+
this.svg.setAttribute("height", String(h));
|
|
156
|
+
}
|
|
157
|
+
this.svg.replaceChildren();
|
|
158
|
+
const defs = el("defs", {}, this.svg);
|
|
159
|
+
let clip = {};
|
|
160
|
+
if (this.opts.radius != null) {
|
|
161
|
+
el("clipPath", { id: `${this.id}-clip` }, defs).append(el("rect", { x: 0, y: 0, width: w, height: this.host.clientHeight, rx: this.opts.radius }, null));
|
|
162
|
+
clip = { "clip-path": `url(#${this.id}-clip)` };
|
|
163
|
+
}
|
|
164
|
+
const grad = el("linearGradient", { id: `${this.id}-band` }, defs);
|
|
165
|
+
el("stop", { offset: "0", "stop-color": "#fff", "stop-opacity": "0" }, grad);
|
|
166
|
+
el("stop", { offset: ".5", "stop-color": "#fff", "stop-opacity": ".85" }, grad);
|
|
167
|
+
el("stop", { offset: "1", "stop-color": "#fff", "stop-opacity": "0" }, grad);
|
|
168
|
+
const mask = el("mask", { id: `${this.id}-bandm` }, defs);
|
|
169
|
+
this.bandG = el("g", {}, mask);
|
|
170
|
+
el("rect", {
|
|
171
|
+
x: -w * 0.3,
|
|
172
|
+
y: -h,
|
|
173
|
+
width: w * 0.6,
|
|
174
|
+
height: h * 3,
|
|
175
|
+
fill: `url(#${this.id}-band)`,
|
|
176
|
+
transform: "rotate(-14)",
|
|
177
|
+
}, this.bandG);
|
|
178
|
+
const body = el("g", { ...clip, fill: "none", "stroke-linecap": "round" }, this.svg);
|
|
179
|
+
if (!this.opts.ghost) {
|
|
180
|
+
const under = el("g", { stroke: p.un }, body);
|
|
181
|
+
for (let y = -0.8; y < h + 2.6; y += 1.5)
|
|
182
|
+
el("path", { d: staticD(y), "stroke-width": "3.4" }, under);
|
|
183
|
+
const seams = el("g", { stroke: p.sh, opacity: ".5" }, body);
|
|
184
|
+
for (let y = 1.6; y < h + 1; y += 2.4)
|
|
185
|
+
el("path", { d: staticD(y), "stroke-width": rand(0.5, 0.8).toFixed(2) }, seams);
|
|
186
|
+
}
|
|
187
|
+
this.live = [];
|
|
188
|
+
const addLive = (y0, node) => {
|
|
189
|
+
const base = [];
|
|
190
|
+
for (let x = -4; x <= w + 4; x += STEP)
|
|
191
|
+
base.push(field(x, y0));
|
|
192
|
+
this.live.push({ y: y0, node, base, disp: 0, vel: 0 });
|
|
193
|
+
};
|
|
194
|
+
const buckets = [p.y0, p.y1, p.y2, p.y3, p.y4];
|
|
195
|
+
for (let y = 0.6; y < h + 1; y += this.opts.pitch + rand(-0.15, 0.15)) {
|
|
196
|
+
const bucket = Math.min(4, Math.max(0, Math.round(4.2 - (y / h) * 3 + rand(-0.9, 0.9))));
|
|
197
|
+
addLive(y, el("path", {
|
|
198
|
+
d: staticD(y),
|
|
199
|
+
"stroke-width": rand(0.6, this.opts.ghost ? 1.0 : 1.4).toFixed(2),
|
|
200
|
+
stroke: buckets[bucket],
|
|
201
|
+
}, body));
|
|
202
|
+
}
|
|
203
|
+
const hiG = el("g", { stroke: p.hi, opacity: ".45" }, body);
|
|
204
|
+
for (let i = 0; i < this.opts.hiCount; i++) {
|
|
205
|
+
const y = rand(2, h * this.opts.hiSpan);
|
|
206
|
+
addLive(y, el("path", { d: staticD(y), "stroke-width": rand(0.4, 0.7).toFixed(2) }, hiG));
|
|
207
|
+
}
|
|
208
|
+
this.sheenG = el("g", {
|
|
209
|
+
...clip,
|
|
210
|
+
fill: "none",
|
|
211
|
+
"stroke-linecap": "round",
|
|
212
|
+
stroke: p.hi,
|
|
213
|
+
mask: `url(#${this.id}-bandm)`,
|
|
214
|
+
opacity: 0,
|
|
215
|
+
}, this.svg);
|
|
216
|
+
this.sheen = [];
|
|
217
|
+
for (let i = 0; i < this.live.length; i += 2) {
|
|
218
|
+
const src = this.live[i];
|
|
219
|
+
const node = el("path", { d: staticD(src.y), "stroke-width": "0.9", "stroke-opacity": ".6" }, this.sheenG);
|
|
220
|
+
this.sheen.push({ src, node });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// 一條紗當下的 d:靜態 base × 局部繃緊 + 凹陷(窩)+ 帶動位移(§4.1)
|
|
224
|
+
liveD(yarn) {
|
|
225
|
+
const sT = 2 * (this.w * 0.35) ** 2;
|
|
226
|
+
const sD = 2 * (this.w * 0.22) ** 2;
|
|
227
|
+
const sX = 2 * (this.w * 0.2) ** 2;
|
|
228
|
+
const ten = Math.max(-0.4, this.tension);
|
|
229
|
+
const dy = this.pressY - yarn.y;
|
|
230
|
+
const funnel = ten * dy * 0.5 * Math.exp(-(dy ** 2) / 338);
|
|
231
|
+
let d = "";
|
|
232
|
+
for (let i = 0, x = -4; x <= this.w + 4; x += STEP, i++) {
|
|
233
|
+
const ampl = 1 - 0.5 * ten * Math.exp(-((x - this.pressX) ** 2) / sT);
|
|
234
|
+
const y = yarn.y +
|
|
235
|
+
yarn.base[i] * ampl +
|
|
236
|
+
funnel * Math.exp(-((x - this.pressX) ** 2) / sD) +
|
|
237
|
+
yarn.disp * Math.exp(-((x - this.gx) ** 2) / sX);
|
|
238
|
+
d += `${d ? "L" : "M"}${x},${y.toFixed(2)}`;
|
|
239
|
+
}
|
|
240
|
+
return d;
|
|
241
|
+
}
|
|
242
|
+
track(e, enter) {
|
|
243
|
+
const r = this.host.getBoundingClientRect();
|
|
244
|
+
const x = e.clientX - r.left;
|
|
245
|
+
const y = e.clientY - r.top;
|
|
246
|
+
const p = this.pointer;
|
|
247
|
+
const vx = p ? x - p.x : 0;
|
|
248
|
+
const vy = p ? y - p.y : 0;
|
|
249
|
+
const speed = Math.hypot(vx, vy);
|
|
250
|
+
this.pointer = { x, y, vx, vy, speed: p ? p.speed * 0.6 + speed * 0.4 : 0 };
|
|
251
|
+
if (enter) {
|
|
252
|
+
this.bx = x;
|
|
253
|
+
this.gx = x;
|
|
254
|
+
this.gy = y;
|
|
255
|
+
}
|
|
256
|
+
this.wake();
|
|
257
|
+
}
|
|
258
|
+
release() {
|
|
259
|
+
if (!this.pressed)
|
|
260
|
+
return;
|
|
261
|
+
this.pressed = false;
|
|
262
|
+
// 指標已拖出 → 不觸發 click,視覺安靜鬆開;過衝+掃光是「確認觸發」的專屬語言
|
|
263
|
+
if (this.pointer == null) {
|
|
264
|
+
this.wake();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this.tenV -= 0.035;
|
|
268
|
+
if (!this.reduced)
|
|
269
|
+
this.sweep = { from: this.pressX, dir: (this.pointer.vx ?? 1) >= 0 ? 1 : -1, t0: performance.now() };
|
|
270
|
+
this.wake();
|
|
271
|
+
}
|
|
272
|
+
tick(now) {
|
|
273
|
+
const p = this.pointer;
|
|
274
|
+
if (p != null) {
|
|
275
|
+
this.gx += (p.x - this.gx) * 0.25;
|
|
276
|
+
this.gy += (p.y - this.gy) * 0.25;
|
|
277
|
+
p.speed *= 0.92;
|
|
278
|
+
}
|
|
279
|
+
let pressDrift = false;
|
|
280
|
+
if (this.pressed && p != null) {
|
|
281
|
+
const dx = this.gx - this.pressX;
|
|
282
|
+
const dyP = this.gy - this.pressY;
|
|
283
|
+
this.pressX += dx * 0.18;
|
|
284
|
+
this.pressY += dyP * 0.18;
|
|
285
|
+
pressDrift = Math.abs(dx) + Math.abs(dyP) > 0.05;
|
|
286
|
+
}
|
|
287
|
+
let moving = false;
|
|
288
|
+
const vh = (this.opts.fixedHeight ? this.host.clientHeight : this.h) + 10;
|
|
289
|
+
const vy = p != null && !this.pressed ? Math.max(-1.8, Math.min(1.8, (p.vy ?? 0) * 0.28)) : 0;
|
|
290
|
+
for (const yarn of this.live) {
|
|
291
|
+
if (yarn.y > vh)
|
|
292
|
+
continue;
|
|
293
|
+
const target = p != null ? vy * Math.exp(-((yarn.y - this.gy) ** 2) / 200) : 0;
|
|
294
|
+
yarn.vel += (target - yarn.disp) * 0.13 - yarn.vel * 0.24;
|
|
295
|
+
yarn.disp += yarn.vel;
|
|
296
|
+
if (Math.abs(yarn.disp) > 1e-3 || Math.abs(yarn.vel) > 1e-3)
|
|
297
|
+
moving = true;
|
|
298
|
+
}
|
|
299
|
+
if (p != null)
|
|
300
|
+
p.vy = 0;
|
|
301
|
+
this.tenV += ((this.pressed && p != null ? 1 : 0) - this.tension) * 0.07 - this.tenV * 0.16;
|
|
302
|
+
this.tension += this.tenV;
|
|
303
|
+
const tensionActive = Math.abs(this.tension) > 1e-3 || Math.abs(this.tenV) > 1e-3 || pressDrift;
|
|
304
|
+
if (moving || tensionActive) {
|
|
305
|
+
for (const yarn of this.live)
|
|
306
|
+
if (yarn.y <= vh)
|
|
307
|
+
yarn.node.setAttribute("d", this.liveD(yarn));
|
|
308
|
+
for (const s of this.sheen)
|
|
309
|
+
if (s.src.y <= vh)
|
|
310
|
+
s.node.setAttribute("d", this.liveD(s.src));
|
|
311
|
+
}
|
|
312
|
+
let bandT = 0;
|
|
313
|
+
if (this.sweep != null) {
|
|
314
|
+
const t = (now - this.sweep.t0) / 640;
|
|
315
|
+
if (t >= 1)
|
|
316
|
+
this.sweep = null;
|
|
317
|
+
else {
|
|
318
|
+
const ease = 1 - (1 - t) ** 3;
|
|
319
|
+
this.bx = this.sweep.from + this.sweep.dir * ease * this.w * 0.9;
|
|
320
|
+
bandT = 0.9 * (1 - t);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (this.sweep == null && p != null && !this.disabled()) {
|
|
324
|
+
this.bx += (p.x - this.bx) * 0.16;
|
|
325
|
+
bandT = this.pressed ? 0.15 : Math.min(this.opts.bandMax, 0.3 + p.speed / 7);
|
|
326
|
+
}
|
|
327
|
+
this.bandOp += (bandT - this.bandOp) * 0.15;
|
|
328
|
+
this.bandG.setAttribute("transform", `translate(${this.bx.toFixed(1)},0)`);
|
|
329
|
+
this.sheenG.setAttribute("opacity", this.bandOp.toFixed(3));
|
|
330
|
+
const active = p != null || this.pressed || this.sweep != null || moving || tensionActive || this.bandOp > 0.005;
|
|
331
|
+
this.raf = active ? requestAnimationFrame((n) => this.tick(n)) : 0;
|
|
332
|
+
}
|
|
333
|
+
wake() {
|
|
334
|
+
if (!this.raf && !this.reduced && this.built)
|
|
335
|
+
this.raf = requestAnimationFrame((n) => this.tick(n));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
import type { CompiledStyles, InlineStyles, StyleXArray } from "@stylexjs/stylex/lib/types/StyleXTypes";
|
|
3
|
+
import type { CSSProperties } from "react";
|
|
4
|
+
export type StyleArg = StyleXArray<(null | undefined | CompiledStyles) | boolean | Readonly<[CompiledStyles, InlineStyles]>>;
|
|
5
|
+
type CallerStyleProps = {
|
|
6
|
+
className?: string;
|
|
7
|
+
style?: CSSProperties;
|
|
8
|
+
};
|
|
9
|
+
/** Merges StyleX output with the caller's own className/style instead of replacing them. */
|
|
10
|
+
export declare function styled(caller: CallerStyleProps, ...styles: StyleArg[]): {
|
|
11
|
+
className?: string;
|
|
12
|
+
style?: CSSProperties;
|
|
13
|
+
};
|
|
14
|
+
export declare const reset: Readonly<{
|
|
15
|
+
readonly control: Readonly<{
|
|
16
|
+
readonly appearance: stylex.StyleXClassNameFor<"appearance", "none">;
|
|
17
|
+
readonly margin: stylex.StyleXClassNameFor<"margin", 0>;
|
|
18
|
+
readonly padding: stylex.StyleXClassNameFor<"padding", 0>;
|
|
19
|
+
readonly borderWidth: stylex.StyleXClassNameFor<"borderWidth", 0>;
|
|
20
|
+
readonly backgroundColor: stylex.StyleXClassNameFor<"backgroundColor", "transparent">;
|
|
21
|
+
readonly color: stylex.StyleXClassNameFor<"color", "inherit">;
|
|
22
|
+
readonly fontFamily: stylex.StyleXClassNameFor<"fontFamily", "inherit">;
|
|
23
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "inherit">;
|
|
24
|
+
readonly fontWeight: stylex.StyleXClassNameFor<"fontWeight", "inherit">;
|
|
25
|
+
readonly lineHeight: stylex.StyleXClassNameFor<"lineHeight", "inherit">;
|
|
26
|
+
readonly textAlign: stylex.StyleXClassNameFor<"textAlign", "inherit">;
|
|
27
|
+
}>;
|
|
28
|
+
}>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
/** Merges StyleX output with the caller's own className/style instead of replacing them. */
|
|
3
|
+
export function styled(caller, ...styles) {
|
|
4
|
+
const applied = stylex.props(...styles);
|
|
5
|
+
const className = [applied.className, caller.className].filter(Boolean).join(" ");
|
|
6
|
+
const hasStyle = applied.style != null || caller.style != null;
|
|
7
|
+
return {
|
|
8
|
+
className: className || undefined,
|
|
9
|
+
style: hasStyle ? { ...applied.style, ...caller.style } : undefined,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
// 原生 button / input / textarea 的 UA 外觀:buttonface 底色、outset 邊框、13px Arial、
|
|
13
|
+
// 內建 padding。`all: unset` 看起來能一次清掉,但 StyleX 0.19 會靜默丟掉那條宣告
|
|
14
|
+
// (編不出任何 CSS),chrome 就整片留在畫面上。逐項重設一次,共用。
|
|
15
|
+
// 用法:放在 stylex.props / styled 的第一個 style 參數,後面的樣式照常覆蓋。
|
|
16
|
+
export const reset = stylex.create({
|
|
17
|
+
control: {
|
|
18
|
+
appearance: "none",
|
|
19
|
+
margin: 0,
|
|
20
|
+
padding: 0,
|
|
21
|
+
borderWidth: 0,
|
|
22
|
+
backgroundColor: "transparent",
|
|
23
|
+
color: "inherit",
|
|
24
|
+
fontFamily: "inherit",
|
|
25
|
+
fontSize: "inherit",
|
|
26
|
+
fontWeight: "inherit",
|
|
27
|
+
lineHeight: "inherit",
|
|
28
|
+
textAlign: "inherit",
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useSvgId(prefix: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useAnimationFrame(active: boolean, onFrame: (elapsedMs: number) => void): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
export function useAnimationFrame(active, onFrame) {
|
|
3
|
+
const callback = useRef(onFrame);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
callback.current = onFrame;
|
|
6
|
+
});
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!active)
|
|
9
|
+
return;
|
|
10
|
+
let frame = 0;
|
|
11
|
+
let start = 0;
|
|
12
|
+
const tick = (now) => {
|
|
13
|
+
if (!start)
|
|
14
|
+
start = now;
|
|
15
|
+
callback.current(now - start);
|
|
16
|
+
frame = requestAnimationFrame(tick);
|
|
17
|
+
};
|
|
18
|
+
frame = requestAnimationFrame(tick);
|
|
19
|
+
return () => cancelAnimationFrame(frame);
|
|
20
|
+
}, [active]);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useControllableState<T>(controlled: T | undefined, defaultValue: T, onChange?: (value: T) => void): [T, (value: T | ((prev: T) => T)) => void];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
export function useControllableState(controlled, defaultValue, onChange) {
|
|
3
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
4
|
+
const value = controlled !== undefined ? controlled : uncontrolled;
|
|
5
|
+
const valueRef = useRef(value);
|
|
6
|
+
valueRef.current = value;
|
|
7
|
+
const set = useCallback((next) => {
|
|
8
|
+
const resolved = typeof next === "function" ? next(valueRef.current) : next;
|
|
9
|
+
valueRef.current = resolved;
|
|
10
|
+
setUncontrolled(resolved);
|
|
11
|
+
onChange?.(resolved);
|
|
12
|
+
}, [onChange]);
|
|
13
|
+
return [value, set];
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
export function useCopy(resetAfterMs = 2000) {
|
|
3
|
+
const [copied, setCopied] = useState(false);
|
|
4
|
+
const timer = useRef(undefined);
|
|
5
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
6
|
+
const copy = useCallback(async (value) => {
|
|
7
|
+
try {
|
|
8
|
+
await navigator.clipboard.writeText(value);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
setCopied(true);
|
|
14
|
+
clearTimeout(timer.current);
|
|
15
|
+
timer.current = setTimeout(() => setCopied(false), resetAfterMs);
|
|
16
|
+
return true;
|
|
17
|
+
}, [resetAfterMs]);
|
|
18
|
+
return { copied, copy };
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const TAU = Math.PI * 2;
|
|
2
|
+
function sample(fn) {
|
|
3
|
+
let d = "";
|
|
4
|
+
for (let x = 2; x <= 46; x += 1.5)
|
|
5
|
+
d += `${d ? "L" : "M"}${x},${fn(x).toFixed(1)}`;
|
|
6
|
+
return d;
|
|
7
|
+
}
|
|
8
|
+
// One fibre, four states: flat at rest, vibrating with the mic level while
|
|
9
|
+
// listening, coiled into a rolling telephone cord while thinking, and carrying
|
|
10
|
+
// a travelling wave while speaking.
|
|
11
|
+
export function voicePath(state, t, level = 0.4) {
|
|
12
|
+
if (state === "idle")
|
|
13
|
+
return "M2,12 L46,12";
|
|
14
|
+
if (state === "thinking") {
|
|
15
|
+
let d = "";
|
|
16
|
+
const loops = 3;
|
|
17
|
+
const span = 36;
|
|
18
|
+
for (let a = -0.6; a <= loops * TAU + 0.6; a += 0.12) {
|
|
19
|
+
const b = 4 + 0.5 * Math.sin(t * 1.8);
|
|
20
|
+
const x = 6 + (span * a) / (loops * TAU) - b * 0.55 * Math.sin(a + t * 2);
|
|
21
|
+
const y = 13.5 - b * 0.75 * Math.cos(a + t * 2);
|
|
22
|
+
d += `${d ? "L" : "M"}${x.toFixed(2)},${y.toFixed(2)}`;
|
|
23
|
+
}
|
|
24
|
+
return d;
|
|
25
|
+
}
|
|
26
|
+
if (state === "listening") {
|
|
27
|
+
return sample((x) => 12 + 8.5 * level * Math.sin((x / 48) * Math.PI) * Math.sin(x / 2.6 + t * 7));
|
|
28
|
+
}
|
|
29
|
+
return sample((x) => 12 + 5.5 * Math.sin((x / 48) * Math.PI) * Math.sin((x / 7.5) * TAU - t * 5));
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const WEAVE_SEED = 10075;
|
|
2
|
+
export type Rand = (a: number, b: number) => number;
|
|
3
|
+
export declare function weaveRand(seed?: number): Rand;
|
|
4
|
+
export interface Strand {
|
|
5
|
+
d: string;
|
|
6
|
+
sw: string;
|
|
7
|
+
}
|
|
8
|
+
export interface FaceStrand extends Strand {
|
|
9
|
+
bucket: 0 | 1 | 2 | 3 | 4;
|
|
10
|
+
}
|
|
11
|
+
export interface WeaveLayers {
|
|
12
|
+
under: Strand[];
|
|
13
|
+
seams: Strand[];
|
|
14
|
+
face: FaceStrand[];
|
|
15
|
+
hi: Strand[];
|
|
16
|
+
}
|
|
17
|
+
export interface WeaveOptions {
|
|
18
|
+
w: number;
|
|
19
|
+
h: number;
|
|
20
|
+
s?: number;
|
|
21
|
+
ox?: number;
|
|
22
|
+
oy?: number;
|
|
23
|
+
pitch?: number;
|
|
24
|
+
hiCount?: number;
|
|
25
|
+
hiSpan?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildWeave(opts: WeaveOptions, rand: Rand): WeaveLayers;
|
|
28
|
+
export declare function buildThread(pts: [number, number][], rand: Rand): string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// 織體幾何(TEXTURE-GUIDE §3):固定種子 → 同尺寸恆定的布。
|
|
2
|
+
// 純函式、無 DOM,可在 module scope 預算(SSR 安全)。
|
|
3
|
+
// 參考實作:components/button/prototype.html(唯一真相)。
|
|
4
|
+
export const WEAVE_SEED = 10075;
|
|
5
|
+
function mulberry32(seed) {
|
|
6
|
+
let a = seed >>> 0;
|
|
7
|
+
return () => {
|
|
8
|
+
a |= 0;
|
|
9
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
10
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
11
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
12
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// 一個元件一支 rand:weave 與縫線依序消費同一序列,織法才與 prototype 完全一致
|
|
16
|
+
export function weaveRand(seed = WEAVE_SEED) {
|
|
17
|
+
const rng = mulberry32(seed);
|
|
18
|
+
return (a, b) => a + rng() * (b - a);
|
|
19
|
+
}
|
|
20
|
+
export function buildWeave(opts, rand) {
|
|
21
|
+
const { w, h, s = 1, ox = 0, oy = 0, pitch = 1.2, hiCount = 4, hiSpan = 0.6 } = opts;
|
|
22
|
+
// 共享波場:兩支長波,相位隨 y 緩慢滑移(§3.2,參數順序照抄)
|
|
23
|
+
const W1 = rand(Math.max(120, w * 0.9), w * 1.8);
|
|
24
|
+
const A1 = rand(1.0, 1.8);
|
|
25
|
+
const P1 = rand(0, 6.28);
|
|
26
|
+
const K1 = rand(0.05, 0.11);
|
|
27
|
+
const W2 = rand(w * 1.6, w * 3);
|
|
28
|
+
const A2 = rand(0.5, 0.9);
|
|
29
|
+
const P2 = rand(0, 6.28);
|
|
30
|
+
const K2 = rand(0.03, 0.07);
|
|
31
|
+
const field = (x, y) => A1 * Math.sin((x / W1) * 6.283 + P1 + y * K1) + A2 * Math.sin((x / W2) * 6.283 + P2 + y * K2);
|
|
32
|
+
// 取樣超出左右邊界 4px,由 clip 裁形(右緣不因 w 非 4 倍數缺一截)
|
|
33
|
+
const staticD = (y0) => {
|
|
34
|
+
let d = "";
|
|
35
|
+
for (let x = -4; x <= w + 4; x += 4)
|
|
36
|
+
d += `${d ? "L" : "M"}${((ox + x) * s).toFixed(2)},${((oy + y0 + field(x, y0)) * s).toFixed(2)}`;
|
|
37
|
+
return d;
|
|
38
|
+
};
|
|
39
|
+
const under = [];
|
|
40
|
+
for (let y = -0.8; y < h + 2.6; y += 1.5)
|
|
41
|
+
under.push({ d: staticD(y), sw: (3.4 * s).toFixed(2) });
|
|
42
|
+
const seams = [];
|
|
43
|
+
for (let y = 1.6; y < h + 1; y += 2.4)
|
|
44
|
+
seams.push({ d: staticD(y), sw: (rand(0.5, 0.8) * s).toFixed(2) });
|
|
45
|
+
const face = [];
|
|
46
|
+
for (let y = 0.6; y < h + 1; y += pitch + rand(-0.15, 0.15)) {
|
|
47
|
+
const bucket = Math.min(4, Math.max(0, Math.round(4.2 - (y / h) * 3 + rand(-0.9, 0.9))));
|
|
48
|
+
face.push({ d: staticD(y), sw: (rand(0.6, 1.4) * s).toFixed(2), bucket });
|
|
49
|
+
}
|
|
50
|
+
const hi = [];
|
|
51
|
+
for (let i = 0; i < hiCount; i++) {
|
|
52
|
+
const y = rand(2, h * hiSpan);
|
|
53
|
+
hi.push({ d: staticD(y), sw: (rand(0.4, 0.7) * s).toFixed(2) });
|
|
54
|
+
}
|
|
55
|
+
return { under, seams, face, hi };
|
|
56
|
+
}
|
|
57
|
+
// 縫線(checkbox 的勾/橫線):折線沿弧長疊一支長波的法向微彎 — 勾也是線,不是平面 icon
|
|
58
|
+
export function buildThread(pts, rand) {
|
|
59
|
+
const samples = [];
|
|
60
|
+
let L = 0;
|
|
61
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
62
|
+
const [x0, y0] = pts[i];
|
|
63
|
+
const [x1, y1] = pts[i + 1];
|
|
64
|
+
const len = Math.hypot(x1 - x0, y1 - y0);
|
|
65
|
+
const nx = -(y1 - y0) / len;
|
|
66
|
+
const ny = (x1 - x0) / len;
|
|
67
|
+
const n = Math.ceil(len);
|
|
68
|
+
for (let j = i === 0 ? 0 : 1; j <= n; j++) {
|
|
69
|
+
const t = j / n;
|
|
70
|
+
samples.push({ x: x0 + (x1 - x0) * t, y: y0 + (y1 - y0) * t, nx, ny, l: L + len * t });
|
|
71
|
+
}
|
|
72
|
+
L += len;
|
|
73
|
+
}
|
|
74
|
+
const A = rand(0.25, 0.45);
|
|
75
|
+
const WL = rand(L * 1.2, L * 2);
|
|
76
|
+
const PH = rand(0, 6.28);
|
|
77
|
+
let d = "";
|
|
78
|
+
for (const p of samples) {
|
|
79
|
+
const off = A * Math.sin((p.l / WL) * 6.283 + PH);
|
|
80
|
+
d += `${d ? "L" : "M"}${(p.x + p.nx * off).toFixed(2)},${(p.y + p.ny * off).toFixed(2)}`;
|
|
81
|
+
}
|
|
82
|
+
return d;
|
|
83
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* @anyknown/ui — 客製 scrollbar(Ledger)
|
|
2
|
+
* 細、圓、安靜:thumb 是一條線(borderStrong),hover 才加深;track 透明。
|
|
3
|
+
* Chromium/Safari 走 ::-webkit-scrollbar;Firefox 走標準屬性(兩者互斥,用 @supports 分流)。 */
|
|
4
|
+
|
|
5
|
+
::-webkit-scrollbar {
|
|
6
|
+
width: 10px;
|
|
7
|
+
height: 10px;
|
|
8
|
+
}
|
|
9
|
+
::-webkit-scrollbar-thumb {
|
|
10
|
+
background: var(--ak-border-strong, #c8c3b4);
|
|
11
|
+
border-radius: 999px;
|
|
12
|
+
border: 3px solid transparent;
|
|
13
|
+
background-clip: padding-box;
|
|
14
|
+
}
|
|
15
|
+
::-webkit-scrollbar-thumb:hover {
|
|
16
|
+
background: var(--ak-text-faint, #9c958a);
|
|
17
|
+
background-clip: padding-box;
|
|
18
|
+
}
|
|
19
|
+
::-webkit-scrollbar-track,
|
|
20
|
+
::-webkit-scrollbar-corner {
|
|
21
|
+
background: transparent;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
25
|
+
* {
|
|
26
|
+
scrollbar-width: thin;
|
|
27
|
+
scrollbar-color: var(--ak-border-strong, #c8c3b4) transparent;
|
|
28
|
+
}
|
|
29
|
+
}
|