@rogieking/figui3 9.0.1 → 9.0.3
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/.cursor/skills/fig-editor/components.md +2 -2
- package/.cursor/skills/fig-editor/reference.md +2 -1
- package/.cursor/skills/figui3/components.md +2 -2
- package/.cursor/skills/figui3/reference.md +1 -1
- package/README.md +38 -5
- package/components.css +7 -2
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +2 -2
- package/dist/fig.css +1 -1
- package/dist/fig.js +5 -5
- package/fig-editor.css +4 -0
- package/fig-editor.js +14 -0
- package/fig.js +261 -54
- package/package.json +1 -1
package/fig-editor.css
CHANGED
package/fig-editor.js
CHANGED
|
@@ -494,6 +494,7 @@ figEditorDefineElement("fig-select-options", FigSelectOptions);
|
|
|
494
494
|
/**
|
|
495
495
|
* A dropdown-styled select.
|
|
496
496
|
* @attr {string} variant - Visual style. Use `ghost` for a borderless control.
|
|
497
|
+
* @attr {string} size - Control size. Use `large` for a 32px-tall control.
|
|
497
498
|
*/
|
|
498
499
|
class FigSelect extends HTMLElement {
|
|
499
500
|
#button = null;
|
|
@@ -537,6 +538,7 @@ class FigSelect extends HTMLElement {
|
|
|
537
538
|
"closedby",
|
|
538
539
|
"open",
|
|
539
540
|
"variant",
|
|
541
|
+
"size",
|
|
540
542
|
"aria-label",
|
|
541
543
|
];
|
|
542
544
|
}
|
|
@@ -564,6 +566,7 @@ class FigSelect extends HTMLElement {
|
|
|
564
566
|
this.#ensurePanelSlotAttrs();
|
|
565
567
|
this.#syncOptionsFromAttribute();
|
|
566
568
|
this.#syncDisabled();
|
|
569
|
+
this.#syncSize();
|
|
567
570
|
this.#syncPopupAttrs();
|
|
568
571
|
this.#syncValue();
|
|
569
572
|
this.#setupListeners();
|
|
@@ -593,6 +596,10 @@ class FigSelect extends HTMLElement {
|
|
|
593
596
|
this.#syncDisabled();
|
|
594
597
|
return;
|
|
595
598
|
}
|
|
599
|
+
if (name === "size") {
|
|
600
|
+
this.#syncSize();
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
596
603
|
if (name === "open") {
|
|
597
604
|
if (newValue === null || newValue === "false") this.#closeList();
|
|
598
605
|
else this.#openList();
|
|
@@ -611,6 +618,13 @@ class FigSelect extends HTMLElement {
|
|
|
611
618
|
this.#button?.blur();
|
|
612
619
|
}
|
|
613
620
|
|
|
621
|
+
#syncSize() {
|
|
622
|
+
if (!this.#button) return;
|
|
623
|
+
const size = this.getAttribute("size");
|
|
624
|
+
if (size) this.#button.setAttribute("size", size);
|
|
625
|
+
else this.#button.removeAttribute("size");
|
|
626
|
+
}
|
|
627
|
+
|
|
614
628
|
#isMenuChild(node) {
|
|
615
629
|
return (
|
|
616
630
|
node?.nodeType === 1 &&
|
package/fig.js
CHANGED
|
@@ -906,6 +906,7 @@ figDefineElement("fig-button", FigButton);
|
|
|
906
906
|
* @attr {string} type - The dropdown type: "select" (default) or "dropdown"
|
|
907
907
|
* @attr {string} value - The currently selected value
|
|
908
908
|
* @attr {string} variant - Visual style. Use `ghost` for a borderless control.
|
|
909
|
+
* @attr {string} size - Control size. Use `large` for a 32px-tall control.
|
|
909
910
|
*/
|
|
910
911
|
class FigDropdown extends HTMLElement {
|
|
911
912
|
#label = "Menu";
|
|
@@ -1104,7 +1105,7 @@ class FigDropdown extends HTMLElement {
|
|
|
1104
1105
|
this.setAttribute("value", value);
|
|
1105
1106
|
}
|
|
1106
1107
|
static get observedAttributes() {
|
|
1107
|
-
return ["value", "type", "label", "disabled", "variant"];
|
|
1108
|
+
return ["value", "type", "label", "disabled", "variant", "size"];
|
|
1108
1109
|
}
|
|
1109
1110
|
#syncDisabled() {
|
|
1110
1111
|
const disabled =
|
|
@@ -14054,15 +14055,18 @@ figDefineElement("fig-input-file", FigInputFile);
|
|
|
14054
14055
|
|
|
14055
14056
|
/**
|
|
14056
14057
|
* A bezier / spring easing curve editor with draggable control points.
|
|
14057
|
-
* @attr {string} value - Bezier
|
|
14058
|
+
* @attr {string} value - Bezier/spring shorthand or a serialized easing object
|
|
14058
14059
|
* @attr {string} mode - Optional editor constraint: "bezier" or "spring".
|
|
14059
14060
|
* @attr {number} precision - Decimal places for output values (default 2)
|
|
14060
14061
|
* @attr {boolean} edit - Show the editor and custom preset options (default true; set "false" for presets only)
|
|
14062
|
+
* @attr {boolean} presets - Show the preset selector (default true)
|
|
14063
|
+
* @attr {boolean} text - Show the manual value input (default true)
|
|
14064
|
+
* @property {object|null} spring - Structured spring configuration when in spring mode
|
|
14061
14065
|
*/
|
|
14062
14066
|
class FigEasingCurve extends HTMLElement {
|
|
14063
14067
|
#cp1 = { x: 0.42, y: 0 };
|
|
14064
14068
|
#cp2 = { x: 0.58, y: 1 };
|
|
14065
|
-
#spring = { stiffness: 200, damping: 15, mass: 1 };
|
|
14069
|
+
#spring = { stiffness: 200, damping: 15, mass: 1, initialVelocity: 0 };
|
|
14066
14070
|
#mode = "bezier";
|
|
14067
14071
|
#precision = 2;
|
|
14068
14072
|
#isDragging = null;
|
|
@@ -14155,7 +14159,15 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14155
14159
|
];
|
|
14156
14160
|
|
|
14157
14161
|
static get observedAttributes() {
|
|
14158
|
-
return [
|
|
14162
|
+
return [
|
|
14163
|
+
"value",
|
|
14164
|
+
"mode",
|
|
14165
|
+
"precision",
|
|
14166
|
+
"aspect-ratio",
|
|
14167
|
+
"edit",
|
|
14168
|
+
"presets",
|
|
14169
|
+
"text",
|
|
14170
|
+
];
|
|
14159
14171
|
}
|
|
14160
14172
|
|
|
14161
14173
|
connectedCallback() {
|
|
@@ -14191,12 +14203,15 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14191
14203
|
return;
|
|
14192
14204
|
}
|
|
14193
14205
|
|
|
14194
|
-
if (name === "edit") {
|
|
14206
|
+
if (name === "edit" || name === "presets" || name === "text") {
|
|
14195
14207
|
if (this.isConnected) this.#render();
|
|
14196
14208
|
return;
|
|
14197
14209
|
}
|
|
14198
14210
|
|
|
14199
14211
|
if (name === "mode") {
|
|
14212
|
+
this.#applyModeConstraint();
|
|
14213
|
+
const value = this.getAttribute("value");
|
|
14214
|
+
if (value) this.#parseValue(value);
|
|
14200
14215
|
this.#applyModeConstraint();
|
|
14201
14216
|
this.#presetName = this.#matchPreset();
|
|
14202
14217
|
if (this.isConnected) this.#render();
|
|
@@ -14222,8 +14237,9 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14222
14237
|
|
|
14223
14238
|
get value() {
|
|
14224
14239
|
if (this.#mode === "spring") {
|
|
14225
|
-
const { stiffness, damping, mass } = this.#spring;
|
|
14226
|
-
|
|
14240
|
+
const { stiffness, damping, mass, initialVelocity = 0 } = this.#spring;
|
|
14241
|
+
const velocity = initialVelocity ? `, ${initialVelocity}` : "";
|
|
14242
|
+
return `spring(${stiffness}, ${damping}, ${mass}${velocity})`;
|
|
14227
14243
|
}
|
|
14228
14244
|
const p = this.#precision;
|
|
14229
14245
|
return `${this.#cp1.x.toFixed(p)}, ${this.#cp1.y.toFixed(p)}, ${this.#cp2.x.toFixed(p)}, ${this.#cp2.y.toFixed(p)}`;
|
|
@@ -14251,9 +14267,39 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14251
14267
|
}
|
|
14252
14268
|
|
|
14253
14269
|
set value(v) {
|
|
14270
|
+
if (v && typeof v === "object") {
|
|
14271
|
+
const normalized = this.#normalizeStructuredValue(v);
|
|
14272
|
+
if (!normalized) {
|
|
14273
|
+
throw new TypeError("Invalid easing value or mode/type mismatch");
|
|
14274
|
+
}
|
|
14275
|
+
const serialized =
|
|
14276
|
+
normalized.type === "spring"
|
|
14277
|
+
? { type: "spring", ...normalized.spring }
|
|
14278
|
+
: {
|
|
14279
|
+
type: "bezier",
|
|
14280
|
+
x1: normalized.bezier[0],
|
|
14281
|
+
y1: normalized.bezier[1],
|
|
14282
|
+
x2: normalized.bezier[2],
|
|
14283
|
+
y2: normalized.bezier[3],
|
|
14284
|
+
};
|
|
14285
|
+
this.setAttribute("value", JSON.stringify(serialized));
|
|
14286
|
+
return;
|
|
14287
|
+
}
|
|
14254
14288
|
this.setAttribute("value", v);
|
|
14255
14289
|
}
|
|
14256
14290
|
|
|
14291
|
+
get spring() {
|
|
14292
|
+
if (this.#mode !== "spring") return null;
|
|
14293
|
+
return { ...this.#spring, initialVelocity: this.#spring.initialVelocity ?? 0 };
|
|
14294
|
+
}
|
|
14295
|
+
|
|
14296
|
+
set spring(value) {
|
|
14297
|
+
if (!value || typeof value !== "object") {
|
|
14298
|
+
throw new TypeError("spring must be an object");
|
|
14299
|
+
}
|
|
14300
|
+
this.value = { ...value, type: "spring" };
|
|
14301
|
+
}
|
|
14302
|
+
|
|
14257
14303
|
#constrainedMode() {
|
|
14258
14304
|
const mode = this.getAttribute("mode")?.trim().toLowerCase();
|
|
14259
14305
|
return mode === "bezier" || mode === "spring" ? mode : null;
|
|
@@ -14269,15 +14315,114 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14269
14315
|
return !mode || preset.type === mode;
|
|
14270
14316
|
}
|
|
14271
14317
|
|
|
14272
|
-
#
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14318
|
+
#normalizeStructuredValue(value) {
|
|
14319
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
14320
|
+
const constrainedMode = this.#constrainedMode();
|
|
14321
|
+
const declaredType =
|
|
14322
|
+
typeof value.type === "string" ? value.type.trim().toLowerCase() : null;
|
|
14323
|
+
if (declaredType && declaredType !== "bezier" && declaredType !== "spring") {
|
|
14324
|
+
return null;
|
|
14325
|
+
}
|
|
14326
|
+
if (declaredType && constrainedMode && declaredType !== constrainedMode) {
|
|
14327
|
+
return null;
|
|
14328
|
+
}
|
|
14329
|
+
const type = declaredType || constrainedMode;
|
|
14330
|
+
if (!type) return null;
|
|
14331
|
+
|
|
14332
|
+
if (type === "spring") {
|
|
14333
|
+
const spring = {
|
|
14334
|
+
mass: Number(value.mass),
|
|
14335
|
+
stiffness: Number(value.stiffness),
|
|
14336
|
+
damping: Number(value.damping),
|
|
14337
|
+
initialVelocity:
|
|
14338
|
+
value.initialVelocity === undefined
|
|
14339
|
+
? 0
|
|
14340
|
+
: Number(value.initialVelocity),
|
|
14341
|
+
};
|
|
14342
|
+
if (
|
|
14343
|
+
!Number.isFinite(spring.mass) ||
|
|
14344
|
+
spring.mass <= 0 ||
|
|
14345
|
+
!Number.isFinite(spring.stiffness) ||
|
|
14346
|
+
spring.stiffness <= 0 ||
|
|
14347
|
+
!Number.isFinite(spring.damping) ||
|
|
14348
|
+
spring.damping < 0 ||
|
|
14349
|
+
!Number.isFinite(spring.initialVelocity)
|
|
14350
|
+
) {
|
|
14351
|
+
return null;
|
|
14352
|
+
}
|
|
14353
|
+
return { type, spring };
|
|
14354
|
+
}
|
|
14355
|
+
|
|
14356
|
+
const bezier = [value.x1, value.y1, value.x2, value.y2].map(Number);
|
|
14357
|
+
if (
|
|
14358
|
+
bezier.some((part) => !Number.isFinite(part)) ||
|
|
14359
|
+
bezier[0] < 0 ||
|
|
14360
|
+
bezier[0] > 1 ||
|
|
14361
|
+
bezier[2] < 0 ||
|
|
14362
|
+
bezier[2] > 1
|
|
14363
|
+
) {
|
|
14364
|
+
return null;
|
|
14365
|
+
}
|
|
14366
|
+
return { type, bezier };
|
|
14367
|
+
}
|
|
14368
|
+
|
|
14369
|
+
#applyStructuredValue(value) {
|
|
14370
|
+
if (value.type === "spring") {
|
|
14371
|
+
this.#mode = "spring";
|
|
14372
|
+
this.#spring = { ...value.spring };
|
|
14373
|
+
} else {
|
|
14374
|
+
this.#mode = "bezier";
|
|
14375
|
+
[this.#cp1.x, this.#cp1.y, this.#cp2.x, this.#cp2.y] = value.bezier;
|
|
14376
|
+
}
|
|
14377
|
+
}
|
|
14378
|
+
|
|
14379
|
+
#parseSpringString(str) {
|
|
14380
|
+
const match = str.match(/^spring\(\s*(.*?)\s*\)$/i);
|
|
14381
|
+
if (!match) return null;
|
|
14382
|
+
const parts = match[1].split(",").map((part) => Number(part.trim()));
|
|
14383
|
+
if (
|
|
14384
|
+
(parts.length !== 3 && parts.length !== 4) ||
|
|
14385
|
+
parts.some((part) => !Number.isFinite(part))
|
|
14386
|
+
) {
|
|
14387
|
+
return null;
|
|
14388
|
+
}
|
|
14389
|
+
const spring = {
|
|
14390
|
+
stiffness: parts[0],
|
|
14391
|
+
damping: parts[1],
|
|
14392
|
+
mass: parts[2],
|
|
14393
|
+
initialVelocity: parts[3] ?? 0,
|
|
14394
|
+
};
|
|
14395
|
+
if (
|
|
14396
|
+
spring.stiffness <= 0 ||
|
|
14397
|
+
spring.damping < 0 ||
|
|
14398
|
+
spring.mass <= 0
|
|
14399
|
+
) {
|
|
14400
|
+
return null;
|
|
14401
|
+
}
|
|
14402
|
+
return spring;
|
|
14403
|
+
}
|
|
14404
|
+
|
|
14405
|
+
#parseValue(input) {
|
|
14406
|
+
if (input && typeof input === "object") {
|
|
14407
|
+
const normalized = this.#normalizeStructuredValue(input);
|
|
14408
|
+
if (!normalized) return false;
|
|
14409
|
+
this.#applyStructuredValue(normalized);
|
|
14410
|
+
return true;
|
|
14411
|
+
}
|
|
14412
|
+
if (typeof input !== "string") return false;
|
|
14413
|
+
const str = input.trim();
|
|
14414
|
+
if (str.startsWith("{")) {
|
|
14415
|
+
try {
|
|
14416
|
+
return this.#parseValue(JSON.parse(str));
|
|
14417
|
+
} catch {
|
|
14418
|
+
return false;
|
|
14419
|
+
}
|
|
14420
|
+
}
|
|
14421
|
+
|
|
14422
|
+
const spring = this.#parseSpringString(str);
|
|
14423
|
+
if (spring && this.#constrainedMode() !== "bezier") {
|
|
14277
14424
|
this.#mode = "spring";
|
|
14278
|
-
this.#spring
|
|
14279
|
-
this.#spring.damping = parseFloat(springMatch[2]);
|
|
14280
|
-
this.#spring.mass = parseFloat(springMatch[3]);
|
|
14425
|
+
this.#spring = spring;
|
|
14281
14426
|
return true;
|
|
14282
14427
|
}
|
|
14283
14428
|
const parts = str.split(",").map((s) => parseFloat(s.trim()));
|
|
@@ -14316,7 +14461,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14316
14461
|
if (
|
|
14317
14462
|
Math.abs(this.#spring.stiffness - p.spring.stiffness) < ep &&
|
|
14318
14463
|
Math.abs(this.#spring.damping - p.spring.damping) < ep &&
|
|
14319
|
-
Math.abs(this.#spring.mass - p.spring.mass) < ep
|
|
14464
|
+
Math.abs(this.#spring.mass - p.spring.mass) < ep &&
|
|
14465
|
+
Math.abs(this.#spring.initialVelocity ?? 0) < ep
|
|
14320
14466
|
)
|
|
14321
14467
|
return p.name;
|
|
14322
14468
|
}
|
|
@@ -14326,12 +14472,12 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14326
14472
|
// --- Spring simulation ---
|
|
14327
14473
|
|
|
14328
14474
|
#simulateSpring() {
|
|
14329
|
-
const { stiffness, damping, mass } = this.#spring;
|
|
14475
|
+
const { stiffness, damping, mass, initialVelocity = 0 } = this.#spring;
|
|
14330
14476
|
const dt = 0.004;
|
|
14331
14477
|
const maxTime = 5;
|
|
14332
14478
|
const points = [];
|
|
14333
14479
|
let pos = 0,
|
|
14334
|
-
vel =
|
|
14480
|
+
vel = initialVelocity;
|
|
14335
14481
|
for (let t = 0; t <= maxTime; t += dt) {
|
|
14336
14482
|
const force = -stiffness * (pos - 1) - damping * vel;
|
|
14337
14483
|
vel += (force / mass) * dt;
|
|
@@ -14344,12 +14490,12 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14344
14490
|
}
|
|
14345
14491
|
|
|
14346
14492
|
static #springIcon(spring, size = 24) {
|
|
14347
|
-
const { stiffness, damping, mass } = spring;
|
|
14493
|
+
const { stiffness, damping, mass, initialVelocity = 0 } = spring;
|
|
14348
14494
|
const dt = 0.004;
|
|
14349
14495
|
const maxTime = 5;
|
|
14350
14496
|
const pts = [];
|
|
14351
14497
|
let pos = 0,
|
|
14352
|
-
vel =
|
|
14498
|
+
vel = initialVelocity;
|
|
14353
14499
|
for (let t = 0; t <= maxTime; t += dt) {
|
|
14354
14500
|
const force = -stiffness * (pos - 1) - damping * vel;
|
|
14355
14501
|
vel += (force / mass) * dt;
|
|
@@ -14467,6 +14613,14 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14467
14613
|
return this.getAttribute("edit") !== "false";
|
|
14468
14614
|
}
|
|
14469
14615
|
|
|
14616
|
+
#isPresetsEnabled() {
|
|
14617
|
+
return this.getAttribute("presets") !== "false";
|
|
14618
|
+
}
|
|
14619
|
+
|
|
14620
|
+
#isTextEnabled() {
|
|
14621
|
+
return this.getAttribute("text") !== "false";
|
|
14622
|
+
}
|
|
14623
|
+
|
|
14470
14624
|
#render() {
|
|
14471
14625
|
this.classList.toggle("spring-mode", this.#mode === "spring");
|
|
14472
14626
|
this.classList.toggle("bezier-mode", this.#mode !== "spring");
|
|
@@ -14594,13 +14748,26 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14594
14748
|
|
|
14595
14749
|
#createContent() {
|
|
14596
14750
|
const size = 200;
|
|
14597
|
-
const select = this.#createSelect();
|
|
14598
|
-
if (!this.#isEditEnabled()) return [select];
|
|
14599
|
-
const valueInput =
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14751
|
+
const select = this.#isPresetsEnabled() ? this.#createSelect() : null;
|
|
14752
|
+
if (!this.#isEditEnabled()) return select ? [select] : [];
|
|
14753
|
+
const valueInput = this.#isTextEnabled()
|
|
14754
|
+
? this.#mode === "spring"
|
|
14755
|
+
? figCreateElement("fig-input-number", {
|
|
14756
|
+
className: "fig-easing-curve-value-input",
|
|
14757
|
+
value: this.#springBounce().toFixed(2),
|
|
14758
|
+
min: "0",
|
|
14759
|
+
max: "1",
|
|
14760
|
+
step: "0.01",
|
|
14761
|
+
precision: "2",
|
|
14762
|
+
"aria-label": "Spring bounce",
|
|
14763
|
+
full: true,
|
|
14764
|
+
})
|
|
14765
|
+
: figCreateElement("fig-input-text", {
|
|
14766
|
+
className: "fig-easing-curve-value-input",
|
|
14767
|
+
value: this.value,
|
|
14768
|
+
full: true,
|
|
14769
|
+
})
|
|
14770
|
+
: null;
|
|
14604
14771
|
const svgChildren = [
|
|
14605
14772
|
figCreateSvgElement("rect", {
|
|
14606
14773
|
className: "fig-easing-curve-bounds",
|
|
@@ -14686,13 +14853,13 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14686
14853
|
svgChildren,
|
|
14687
14854
|
);
|
|
14688
14855
|
return [
|
|
14689
|
-
select,
|
|
14856
|
+
...(select ? [select] : []),
|
|
14690
14857
|
figCreateElement(
|
|
14691
14858
|
"div",
|
|
14692
14859
|
{ className: "fig-easing-curve-svg-container" },
|
|
14693
14860
|
svg,
|
|
14694
14861
|
),
|
|
14695
|
-
valueInput,
|
|
14862
|
+
...(valueInput ? [valueInput] : []),
|
|
14696
14863
|
];
|
|
14697
14864
|
}
|
|
14698
14865
|
|
|
@@ -14981,7 +15148,43 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14981
15148
|
|
|
14982
15149
|
#syncValueInput() {
|
|
14983
15150
|
if (!this.#valueInput) return;
|
|
14984
|
-
|
|
15151
|
+
const value =
|
|
15152
|
+
this.#mode === "spring"
|
|
15153
|
+
? this.#springBounce().toFixed(2)
|
|
15154
|
+
: this.value;
|
|
15155
|
+
this.#valueInput.setAttribute("value", value);
|
|
15156
|
+
}
|
|
15157
|
+
|
|
15158
|
+
#springBounce() {
|
|
15159
|
+
const { stiffness, damping, mass } = this.#spring;
|
|
15160
|
+
const criticalDamping = 2 * Math.sqrt(stiffness * mass);
|
|
15161
|
+
if (!Number.isFinite(criticalDamping) || criticalDamping <= 0) return 0;
|
|
15162
|
+
return Math.max(0, Math.min(1, 1 - damping / criticalDamping));
|
|
15163
|
+
}
|
|
15164
|
+
|
|
15165
|
+
#setSpringBounce(value) {
|
|
15166
|
+
const bounce = Math.max(0, Math.min(1, Number(value)));
|
|
15167
|
+
const criticalDamping = 2 * Math.sqrt(
|
|
15168
|
+
this.#spring.stiffness * this.#spring.mass,
|
|
15169
|
+
);
|
|
15170
|
+
this.#spring.damping = Number(
|
|
15171
|
+
(criticalDamping * (1 - bounce)).toFixed(4),
|
|
15172
|
+
);
|
|
15173
|
+
}
|
|
15174
|
+
|
|
15175
|
+
#applySpringBounce(value, eventType) {
|
|
15176
|
+
const bounce = Number(value);
|
|
15177
|
+
if (!Number.isFinite(bounce)) {
|
|
15178
|
+
if (eventType === "change") this.#syncValueInput();
|
|
15179
|
+
return;
|
|
15180
|
+
}
|
|
15181
|
+
|
|
15182
|
+
this.#setSpringBounce(bounce);
|
|
15183
|
+
this.#presetName = this.#matchPreset();
|
|
15184
|
+
this.#updatePaths();
|
|
15185
|
+
this.#syncSelect();
|
|
15186
|
+
if (eventType === "change") this.#syncValueInput();
|
|
15187
|
+
this.#emit(eventType);
|
|
14985
15188
|
}
|
|
14986
15189
|
|
|
14987
15190
|
#parseManualBezierValue(value) {
|
|
@@ -14996,16 +15199,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14996
15199
|
}
|
|
14997
15200
|
|
|
14998
15201
|
#parseManualSpringValue(value) {
|
|
14999
|
-
|
|
15000
|
-
/^spring\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/,
|
|
15001
|
-
);
|
|
15002
|
-
if (!match) return null;
|
|
15003
|
-
const spring = {
|
|
15004
|
-
stiffness: Number(match[1]),
|
|
15005
|
-
damping: Number(match[2]),
|
|
15006
|
-
mass: Number(match[3]),
|
|
15007
|
-
};
|
|
15008
|
-
return Object.values(spring).every((part) => part > 0) ? spring : null;
|
|
15202
|
+
return this.#parseSpringString(value);
|
|
15009
15203
|
}
|
|
15010
15204
|
|
|
15011
15205
|
#applyManualValue(value, eventType) {
|
|
@@ -15115,18 +15309,19 @@ class FigEasingCurve extends HTMLElement {
|
|
|
15115
15309
|
const unit = event.shiftKey ? 10 : 1;
|
|
15116
15310
|
|
|
15117
15311
|
if (handleType === "bounce") {
|
|
15312
|
+
const step = unit / 100;
|
|
15118
15313
|
switch (event.key) {
|
|
15119
15314
|
case "ArrowUp":
|
|
15120
|
-
this.#
|
|
15315
|
+
this.#setSpringBounce(this.#springBounce() + step);
|
|
15121
15316
|
break;
|
|
15122
15317
|
case "ArrowDown":
|
|
15123
|
-
this.#
|
|
15318
|
+
this.#setSpringBounce(this.#springBounce() - step);
|
|
15124
15319
|
break;
|
|
15125
15320
|
case "Home":
|
|
15126
|
-
this.#
|
|
15321
|
+
this.#setSpringBounce(0);
|
|
15127
15322
|
break;
|
|
15128
15323
|
case "End":
|
|
15129
|
-
this.#
|
|
15324
|
+
this.#setSpringBounce(1);
|
|
15130
15325
|
break;
|
|
15131
15326
|
default:
|
|
15132
15327
|
return false;
|
|
@@ -15201,6 +15396,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
15201
15396
|
// --- Events ---
|
|
15202
15397
|
|
|
15203
15398
|
#emit(type) {
|
|
15399
|
+
const spring = this.spring;
|
|
15204
15400
|
this.dispatchEvent(
|
|
15205
15401
|
new CustomEvent(type, {
|
|
15206
15402
|
bubbles: true,
|
|
@@ -15209,6 +15405,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
15209
15405
|
value: this.value,
|
|
15210
15406
|
cssValue: this.cssValue,
|
|
15211
15407
|
preset: this.#presetName,
|
|
15408
|
+
...(spring ? { spring } : {}),
|
|
15212
15409
|
},
|
|
15213
15410
|
}),
|
|
15214
15411
|
);
|
|
@@ -15260,7 +15457,14 @@ class FigEasingCurve extends HTMLElement {
|
|
|
15260
15457
|
}
|
|
15261
15458
|
|
|
15262
15459
|
if (this.#select) {
|
|
15460
|
+
this.#select.addEventListener("input", (e) => {
|
|
15461
|
+
e.stopPropagation();
|
|
15462
|
+
});
|
|
15463
|
+
this.#select.addEventListener("optionhover", (e) => {
|
|
15464
|
+
e.stopPropagation();
|
|
15465
|
+
});
|
|
15263
15466
|
this.#select.addEventListener("change", (e) => {
|
|
15467
|
+
e.stopPropagation();
|
|
15264
15468
|
const name = e.detail;
|
|
15265
15469
|
const preset = FigEasingCurve.PRESETS.find((p) => p.name === name);
|
|
15266
15470
|
if (!preset) return;
|
|
@@ -15282,7 +15486,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
15282
15486
|
}
|
|
15283
15487
|
} else if (preset.type === "spring") {
|
|
15284
15488
|
if (preset.spring) {
|
|
15285
|
-
this.#spring = { ...preset.spring };
|
|
15489
|
+
this.#spring = { ...preset.spring, initialVelocity: 0 };
|
|
15286
15490
|
}
|
|
15287
15491
|
this.#presetName = name;
|
|
15288
15492
|
if (this.#mode !== "spring") {
|
|
@@ -15302,14 +15506,20 @@ class FigEasingCurve extends HTMLElement {
|
|
|
15302
15506
|
this.#valueInput.addEventListener("input", (e) => {
|
|
15303
15507
|
e.stopPropagation();
|
|
15304
15508
|
const value = e.detail ?? e.target?.value;
|
|
15305
|
-
if (
|
|
15306
|
-
|
|
15509
|
+
if (this.#mode === "spring") {
|
|
15510
|
+
this.#applySpringBounce(value, "input");
|
|
15511
|
+
} else if (typeof value === "string") {
|
|
15512
|
+
this.#applyManualValue(value, "input");
|
|
15513
|
+
}
|
|
15307
15514
|
});
|
|
15308
15515
|
this.#valueInput.addEventListener("change", (e) => {
|
|
15309
15516
|
e.stopPropagation();
|
|
15310
15517
|
const value = e.detail ?? e.target?.value;
|
|
15311
|
-
if (
|
|
15312
|
-
|
|
15518
|
+
if (this.#mode === "spring") {
|
|
15519
|
+
this.#applySpringBounce(value, "change");
|
|
15520
|
+
} else if (typeof value === "string") {
|
|
15521
|
+
this.#applyManualValue(value, "change");
|
|
15522
|
+
}
|
|
15313
15523
|
});
|
|
15314
15524
|
}
|
|
15315
15525
|
}
|
|
@@ -15397,7 +15607,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
15397
15607
|
this.#isDragging = handleType;
|
|
15398
15608
|
this.classList.toggle("spring-bounce-dragging", handleType === "bounce");
|
|
15399
15609
|
|
|
15400
|
-
const
|
|
15610
|
+
const startBounce = this.#springBounce();
|
|
15401
15611
|
const startStiffness = this.#spring.stiffness;
|
|
15402
15612
|
const startDuration = this.#springDuration;
|
|
15403
15613
|
const startY = e.clientY;
|
|
@@ -15408,10 +15618,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
15408
15618
|
|
|
15409
15619
|
if (handleType === "bounce") {
|
|
15410
15620
|
const dy = e.clientY - startY;
|
|
15411
|
-
this.#
|
|
15412
|
-
1,
|
|
15413
|
-
Math.round(startDamping + dy * 0.15),
|
|
15414
|
-
);
|
|
15621
|
+
this.#setSpringBounce(startBounce - dy / 200);
|
|
15415
15622
|
} else {
|
|
15416
15623
|
const dx = e.clientX - startX;
|
|
15417
15624
|
this.#springDuration = Math.max(
|
package/package.json
CHANGED