@rogieking/figui3 8.0.2 → 8.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/README.md +151 -0
- package/dist/fig-editor.css +1 -1
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +14 -14
- package/dist/fig.js +1 -1
- package/fig-editor.css +0 -1
- package/fig-lab.css +115 -17
- package/fig-lab.js +1475 -1
- package/fig.js +3 -12
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -2040,6 +2040,1466 @@ class PropskitNumber extends HTMLElement {
|
|
|
2040
2040
|
}
|
|
2041
2041
|
figLabDefineElement("propskit-number", PropskitNumber);
|
|
2042
2042
|
|
|
2043
|
+
/**
|
|
2044
|
+
* Compact X/Y editor.
|
|
2045
|
+
*
|
|
2046
|
+
* @attr {number} x - Horizontal value.
|
|
2047
|
+
* @attr {number} y - Vertical value.
|
|
2048
|
+
* @attr {string} default - JSON reset value with x and y.
|
|
2049
|
+
* @attr {string} label - Field label. Empty values use "Position".
|
|
2050
|
+
* @attr {string} units - Set to "percent" to show percentage units.
|
|
2051
|
+
* @attr {boolean|string} disabled - Disables both number inputs.
|
|
2052
|
+
* @attr {string} size - Set to "large" for the expanded row.
|
|
2053
|
+
* @fires input - Composed event with { x, y }.
|
|
2054
|
+
* @fires change - Composed event with { x, y }.
|
|
2055
|
+
*/
|
|
2056
|
+
class PropskitPosition extends HTMLElement {
|
|
2057
|
+
static observedAttributes = [
|
|
2058
|
+
"x",
|
|
2059
|
+
"y",
|
|
2060
|
+
"default",
|
|
2061
|
+
"label",
|
|
2062
|
+
"units",
|
|
2063
|
+
"disabled",
|
|
2064
|
+
"size",
|
|
2065
|
+
];
|
|
2066
|
+
|
|
2067
|
+
#field = null;
|
|
2068
|
+
#xInput = null;
|
|
2069
|
+
#yInput = null;
|
|
2070
|
+
#initialValue = null;
|
|
2071
|
+
#initialized = false;
|
|
2072
|
+
#reflecting = false;
|
|
2073
|
+
#boundHandleInput = this.#handleInputEvent.bind(this, "input");
|
|
2074
|
+
#boundHandleChange = this.#handleInputEvent.bind(this, "change");
|
|
2075
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
2076
|
+
|
|
2077
|
+
connectedCallback() {
|
|
2078
|
+
if (!this.#initialized) {
|
|
2079
|
+
this.#initialValue = this.#readValue();
|
|
2080
|
+
this.#reflectValue(this.#initialValue);
|
|
2081
|
+
this.#initialized = true;
|
|
2082
|
+
}
|
|
2083
|
+
if (!this.#field) this.#render();
|
|
2084
|
+
this.#syncLabel();
|
|
2085
|
+
this.#syncInputs();
|
|
2086
|
+
this.#bindEvents();
|
|
2087
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
2088
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
2089
|
+
figLabConnectPropskitResetMenu(this);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
disconnectedCallback() {
|
|
2093
|
+
this.#unbindEvents();
|
|
2094
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
2095
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
2099
|
+
if (oldValue === newValue || !this.#initialized) return;
|
|
2100
|
+
if ((name === "x" || name === "y") && !this.#reflecting) {
|
|
2101
|
+
this.#syncInputs();
|
|
2102
|
+
} else if (name === "label") {
|
|
2103
|
+
this.#syncLabel();
|
|
2104
|
+
} else if (name === "units") {
|
|
2105
|
+
this.#syncUnits();
|
|
2106
|
+
} else if (name === "disabled") {
|
|
2107
|
+
this.#syncDisabled();
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
#finiteNumber(value, fallback = 50) {
|
|
2112
|
+
const number = Number(value);
|
|
2113
|
+
return Number.isFinite(number) ? number : fallback;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
#readValue() {
|
|
2117
|
+
const x = this.getAttribute("x");
|
|
2118
|
+
const y = this.getAttribute("y");
|
|
2119
|
+
return {
|
|
2120
|
+
x: x === null || !x.trim() ? 50 : this.#finiteNumber(x, 50),
|
|
2121
|
+
y: y === null || !y.trim() ? 50 : this.#finiteNumber(y, 50),
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
#parseDefault(value) {
|
|
2126
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
2127
|
+
try {
|
|
2128
|
+
const parsed = JSON.parse(value);
|
|
2129
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2130
|
+
return null;
|
|
2131
|
+
}
|
|
2132
|
+
return {
|
|
2133
|
+
x: this.#finiteNumber(parsed.x, this.#initialValue?.x ?? 50),
|
|
2134
|
+
y: this.#finiteNumber(parsed.y, this.#initialValue?.y ?? 50),
|
|
2135
|
+
};
|
|
2136
|
+
} catch {
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
#reflectValue(value) {
|
|
2142
|
+
const current = this.#readValue();
|
|
2143
|
+
const normalized = {
|
|
2144
|
+
x: this.#finiteNumber(value?.x, current.x),
|
|
2145
|
+
y: this.#finiteNumber(value?.y, current.y),
|
|
2146
|
+
};
|
|
2147
|
+
this.#reflecting = true;
|
|
2148
|
+
this.setAttribute("x", String(normalized.x));
|
|
2149
|
+
this.setAttribute("y", String(normalized.y));
|
|
2150
|
+
this.#reflecting = false;
|
|
2151
|
+
return normalized;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
#createNumber(axis, value) {
|
|
2155
|
+
const input = document.createElement("fig-input-number");
|
|
2156
|
+
input.setAttribute("data-propskit-position-axis", axis);
|
|
2157
|
+
input.setAttribute("value", String(value));
|
|
2158
|
+
input.setAttribute("min", "0");
|
|
2159
|
+
input.setAttribute("max", "100");
|
|
2160
|
+
input.setAttribute("step", "1");
|
|
2161
|
+
input.setAttribute("precision", "2");
|
|
2162
|
+
if (this.units === "percent") input.setAttribute("units", "%");
|
|
2163
|
+
input.setAttribute("aria-label", `${axis.toUpperCase()} position`);
|
|
2164
|
+
const prepend = document.createElement("span");
|
|
2165
|
+
prepend.setAttribute("slot", "prepend");
|
|
2166
|
+
prepend.textContent = axis.toUpperCase();
|
|
2167
|
+
input.append(prepend);
|
|
2168
|
+
if (figLabBooleanAttribute(this, "disabled")) {
|
|
2169
|
+
input.setAttribute("disabled", "");
|
|
2170
|
+
}
|
|
2171
|
+
return input;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
#render() {
|
|
2175
|
+
const value = this.#readValue();
|
|
2176
|
+
const field = document.createElement("fig-field");
|
|
2177
|
+
field.className = "propskit-position-row";
|
|
2178
|
+
field.setAttribute("direction", "horizontal");
|
|
2179
|
+
const label = document.createElement("label");
|
|
2180
|
+
const xInput = this.#createNumber("x", value.x);
|
|
2181
|
+
const yInput = this.#createNumber("y", value.y);
|
|
2182
|
+
field.append(label, xInput, yInput);
|
|
2183
|
+
this.#field = field;
|
|
2184
|
+
this.#xInput = xInput;
|
|
2185
|
+
this.#yInput = yInput;
|
|
2186
|
+
this.replaceChildren(field);
|
|
2187
|
+
figLabConnectPropskitResetMenu(this);
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
#syncLabel() {
|
|
2191
|
+
const label = this.#field?.querySelector(":scope > label");
|
|
2192
|
+
if (!label) return;
|
|
2193
|
+
const rawLabel = this.getAttribute("label");
|
|
2194
|
+
label.textContent = rawLabel?.trim() || "Position";
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
#syncInputs() {
|
|
2198
|
+
const value = this.#readValue();
|
|
2199
|
+
this.#xInput?.setAttribute("value", String(value.x));
|
|
2200
|
+
this.#yInput?.setAttribute("value", String(value.y));
|
|
2201
|
+
this.#syncUnits();
|
|
2202
|
+
this.#syncDisabled();
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
#syncUnits() {
|
|
2206
|
+
const units = this.units === "percent" ? "%" : null;
|
|
2207
|
+
for (const input of [this.#xInput, this.#yInput]) {
|
|
2208
|
+
if (!input) continue;
|
|
2209
|
+
if (units) input.setAttribute("units", units);
|
|
2210
|
+
else input.removeAttribute("units");
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
#syncDisabled() {
|
|
2215
|
+
const disabled = figLabBooleanAttribute(this, "disabled");
|
|
2216
|
+
this.#xInput?.toggleAttribute("disabled", disabled);
|
|
2217
|
+
this.#yInput?.toggleAttribute("disabled", disabled);
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
#bindEvents() {
|
|
2221
|
+
this.#unbindEvents();
|
|
2222
|
+
this.#xInput?.addEventListener("input", this.#boundHandleInput);
|
|
2223
|
+
this.#xInput?.addEventListener("change", this.#boundHandleChange);
|
|
2224
|
+
this.#yInput?.addEventListener("input", this.#boundHandleInput);
|
|
2225
|
+
this.#yInput?.addEventListener("change", this.#boundHandleChange);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
#unbindEvents() {
|
|
2229
|
+
this.#xInput?.removeEventListener("input", this.#boundHandleInput);
|
|
2230
|
+
this.#xInput?.removeEventListener("change", this.#boundHandleChange);
|
|
2231
|
+
this.#yInput?.removeEventListener("input", this.#boundHandleInput);
|
|
2232
|
+
this.#yInput?.removeEventListener("change", this.#boundHandleChange);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
#handleInputEvent(type, event) {
|
|
2236
|
+
event.stopImmediatePropagation();
|
|
2237
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
2238
|
+
const axis = event.currentTarget?.getAttribute("data-propskit-position-axis");
|
|
2239
|
+
if (axis !== "x" && axis !== "y") return;
|
|
2240
|
+
const value = this.value;
|
|
2241
|
+
value[axis] = this.#finiteNumber(event.currentTarget.value, value[axis]);
|
|
2242
|
+
const detail = this.#reflectValue(value);
|
|
2243
|
+
this.dispatchEvent(
|
|
2244
|
+
new CustomEvent(type, {
|
|
2245
|
+
detail: { ...detail, units: this.units },
|
|
2246
|
+
bubbles: true,
|
|
2247
|
+
cancelable: true,
|
|
2248
|
+
composed: true,
|
|
2249
|
+
}),
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
#handleClick(event) {
|
|
2254
|
+
if (
|
|
2255
|
+
event.target instanceof Element &&
|
|
2256
|
+
event.target.closest("fig-input-number, fig-menu")
|
|
2257
|
+
) {
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
this.focus();
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
get x() {
|
|
2264
|
+
return this.#readValue().x;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
set x(value) {
|
|
2268
|
+
this.setAttribute("x", String(this.#finiteNumber(value, 50)));
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
get y() {
|
|
2272
|
+
return this.#readValue().y;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
set y(value) {
|
|
2276
|
+
this.setAttribute("y", String(this.#finiteNumber(value, 50)));
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
get units() {
|
|
2280
|
+
return this.getAttribute("units")?.trim().toLowerCase() === "percent"
|
|
2281
|
+
? "percent"
|
|
2282
|
+
: "none";
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
set units(value) {
|
|
2286
|
+
this.setAttribute(
|
|
2287
|
+
"units",
|
|
2288
|
+
String(value).trim().toLowerCase() === "none" ? "none" : "percent",
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
get value() {
|
|
2293
|
+
return { ...this.#readValue() };
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
set value(value) {
|
|
2297
|
+
const normalized = this.#reflectValue(value);
|
|
2298
|
+
this.#syncInputs(normalized);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
get defaultValue() {
|
|
2302
|
+
return (
|
|
2303
|
+
this.#parseDefault(this.getAttribute("default")) || {
|
|
2304
|
+
...(this.#initialValue || { x: 50, y: 50 }),
|
|
2305
|
+
}
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
get isDefault() {
|
|
2310
|
+
return figLabPropskitJsonValuesEqual(this.value, this.defaultValue);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
resetToDefault() {
|
|
2314
|
+
const value = this.defaultValue;
|
|
2315
|
+
this.value = value;
|
|
2316
|
+
figLabEmitPropskitReset(this, { ...value, units: this.units });
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
focus(options) {
|
|
2320
|
+
this.#xInput?.focus(options);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
figLabDefineElement("propskit-position", PropskitPosition);
|
|
2324
|
+
|
|
2325
|
+
/**
|
|
2326
|
+
* Collapsible color-point group composed from color and position controls.
|
|
2327
|
+
*
|
|
2328
|
+
* @attr {string} label - Passed to the internal fig-group name.
|
|
2329
|
+
* @attr {boolean|string} collapsible - Internal group collapsibility; defaults true.
|
|
2330
|
+
* @attr {boolean|string} open - Internal group expanded state; defaults true.
|
|
2331
|
+
* @attr {string} size - Passed to both internal PropsKit controls.
|
|
2332
|
+
* @attr {string} value - JSON object with x, y, and color.
|
|
2333
|
+
* @fires input - Composed event with { x, y, color }.
|
|
2334
|
+
* @fires change - Composed event with { x, y, color }.
|
|
2335
|
+
* @fires openchange - Composed event mirroring the internal fig-group.
|
|
2336
|
+
*/
|
|
2337
|
+
class PropskitColorPoint extends HTMLElement {
|
|
2338
|
+
static observedAttributes = [
|
|
2339
|
+
"label",
|
|
2340
|
+
"collapsible",
|
|
2341
|
+
"open",
|
|
2342
|
+
"size",
|
|
2343
|
+
"value",
|
|
2344
|
+
];
|
|
2345
|
+
|
|
2346
|
+
#group = null;
|
|
2347
|
+
#colorControl = null;
|
|
2348
|
+
#positionControl = null;
|
|
2349
|
+
#initialized = false;
|
|
2350
|
+
#reflectingValue = false;
|
|
2351
|
+
#reflectingOpen = false;
|
|
2352
|
+
#boundHandleInput = this.#handleControlEvent.bind(this, "input");
|
|
2353
|
+
#boundHandleChange = this.#handleControlEvent.bind(this, "change");
|
|
2354
|
+
#boundHandleOpenChange = this.#handleOpenChange.bind(this);
|
|
2355
|
+
|
|
2356
|
+
connectedCallback() {
|
|
2357
|
+
if (!this.#initialized) {
|
|
2358
|
+
this.#reflectValue(this.#readValue());
|
|
2359
|
+
this.#initialized = true;
|
|
2360
|
+
}
|
|
2361
|
+
if (!this.#group) this.#render();
|
|
2362
|
+
this.#syncGroupAttributes();
|
|
2363
|
+
this.#syncSize();
|
|
2364
|
+
this.#syncControls(this.#readValue());
|
|
2365
|
+
this.removeEventListener("input", this.#boundHandleInput);
|
|
2366
|
+
this.addEventListener("input", this.#boundHandleInput);
|
|
2367
|
+
this.removeEventListener("change", this.#boundHandleChange);
|
|
2368
|
+
this.addEventListener("change", this.#boundHandleChange);
|
|
2369
|
+
this.removeEventListener("openchange", this.#boundHandleOpenChange);
|
|
2370
|
+
this.addEventListener("openchange", this.#boundHandleOpenChange);
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
disconnectedCallback() {
|
|
2374
|
+
this.removeEventListener("input", this.#boundHandleInput);
|
|
2375
|
+
this.removeEventListener("change", this.#boundHandleChange);
|
|
2376
|
+
this.removeEventListener("openchange", this.#boundHandleOpenChange);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
2380
|
+
if (oldValue === newValue || !this.#initialized) return;
|
|
2381
|
+
if (name === "value") {
|
|
2382
|
+
if (!this.#reflectingValue) {
|
|
2383
|
+
const value = this.#reflectValue(this.#readValue());
|
|
2384
|
+
this.#syncControls(value);
|
|
2385
|
+
}
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
if (name === "size") {
|
|
2389
|
+
this.#syncSize();
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
this.#syncGroupAttributes();
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
#finiteNumber(value, fallback) {
|
|
2396
|
+
const number = Number(value);
|
|
2397
|
+
return Number.isFinite(number) ? number : fallback;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
#normalizeValue(value) {
|
|
2401
|
+
let source = value;
|
|
2402
|
+
if (typeof value === "string") {
|
|
2403
|
+
try {
|
|
2404
|
+
source = JSON.parse(value);
|
|
2405
|
+
} catch {
|
|
2406
|
+
source = null;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
2410
|
+
source = {};
|
|
2411
|
+
}
|
|
2412
|
+
return {
|
|
2413
|
+
x: this.#finiteNumber(source.x, 50),
|
|
2414
|
+
y: this.#finiteNumber(source.y, 50),
|
|
2415
|
+
color:
|
|
2416
|
+
typeof source.color === "string" && source.color.trim()
|
|
2417
|
+
? source.color.trim()
|
|
2418
|
+
: "#D9D9D9",
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
#readValue() {
|
|
2423
|
+
return this.#normalizeValue(this.getAttribute("value"));
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
#reflectValue(value) {
|
|
2427
|
+
const normalized = this.#normalizeValue(value);
|
|
2428
|
+
const serialized = JSON.stringify(normalized);
|
|
2429
|
+
if (this.getAttribute("value") === serialized) return normalized;
|
|
2430
|
+
this.#reflectingValue = true;
|
|
2431
|
+
this.setAttribute("value", serialized);
|
|
2432
|
+
this.#reflectingValue = false;
|
|
2433
|
+
return normalized;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
#render() {
|
|
2437
|
+
const value = this.#readValue();
|
|
2438
|
+
const group = document.createElement("fig-group");
|
|
2439
|
+
const color = document.createElement("propskit-color");
|
|
2440
|
+
const position = document.createElement("propskit-position");
|
|
2441
|
+
group.setAttribute("compact", "");
|
|
2442
|
+
color.setAttribute("label", "Color");
|
|
2443
|
+
color.setAttribute("value", value.color);
|
|
2444
|
+
color.setAttribute("data-propskit-color-point-control", "color");
|
|
2445
|
+
position.setAttribute("label", "Position");
|
|
2446
|
+
position.setAttribute("x", String(value.x));
|
|
2447
|
+
position.setAttribute("y", String(value.y));
|
|
2448
|
+
position.setAttribute("units", "percent");
|
|
2449
|
+
position.setAttribute("data-propskit-color-point-control", "position");
|
|
2450
|
+
this.#group = group;
|
|
2451
|
+
this.#colorControl = color;
|
|
2452
|
+
this.#positionControl = position;
|
|
2453
|
+
this.#syncGroupAttributes();
|
|
2454
|
+
this.#syncSize();
|
|
2455
|
+
group.append(color, position);
|
|
2456
|
+
this.replaceChildren(group);
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
#syncGroupAttributes() {
|
|
2460
|
+
if (!this.#group) return;
|
|
2461
|
+
this.#group.setAttribute("compact", "");
|
|
2462
|
+
const label = this.getAttribute("label")?.trim();
|
|
2463
|
+
if (label) this.#group.setAttribute("name", label);
|
|
2464
|
+
else this.#group.removeAttribute("name");
|
|
2465
|
+
this.#group.toggleAttribute("collapsible", this.collapsible);
|
|
2466
|
+
if (this.collapsible) {
|
|
2467
|
+
this.#group.setAttribute("open", String(this.open));
|
|
2468
|
+
} else {
|
|
2469
|
+
this.#group.removeAttribute("open");
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
#syncSize() {
|
|
2474
|
+
const size = this.getAttribute("size");
|
|
2475
|
+
for (const control of [this.#colorControl, this.#positionControl]) {
|
|
2476
|
+
if (!control) continue;
|
|
2477
|
+
if (size === null) control.removeAttribute("size");
|
|
2478
|
+
else control.setAttribute("size", size);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
#syncControls(value) {
|
|
2483
|
+
const normalized = this.#normalizeValue(value);
|
|
2484
|
+
if (this.#colorControl) this.#colorControl.value = normalized.color;
|
|
2485
|
+
if (this.#positionControl) {
|
|
2486
|
+
this.#positionControl.value = {
|
|
2487
|
+
x: normalized.x,
|
|
2488
|
+
y: normalized.y,
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
#handleControlEvent(type, event) {
|
|
2494
|
+
if (event.target === this) return;
|
|
2495
|
+
const control = event.target?.closest?.(
|
|
2496
|
+
"[data-propskit-color-point-control]",
|
|
2497
|
+
);
|
|
2498
|
+
if (!control || !this.contains(control)) return;
|
|
2499
|
+
event.stopImmediatePropagation();
|
|
2500
|
+
const value = this.value;
|
|
2501
|
+
const kind = control.getAttribute("data-propskit-color-point-control");
|
|
2502
|
+
if (kind === "color") {
|
|
2503
|
+
value.color = control.value || control.getAttribute("value");
|
|
2504
|
+
} else if (kind === "position") {
|
|
2505
|
+
value.x = this.#finiteNumber(control.value?.x, value.x);
|
|
2506
|
+
value.y = this.#finiteNumber(control.value?.y, value.y);
|
|
2507
|
+
} else {
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
const detail = this.#reflectValue(value);
|
|
2511
|
+
this.dispatchEvent(
|
|
2512
|
+
new CustomEvent(type, {
|
|
2513
|
+
detail: { ...detail, units: "percent" },
|
|
2514
|
+
bubbles: true,
|
|
2515
|
+
cancelable: true,
|
|
2516
|
+
composed: true,
|
|
2517
|
+
}),
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
#handleOpenChange(event) {
|
|
2522
|
+
if (event.target !== this.#group || this.#reflectingOpen) return;
|
|
2523
|
+
event.stopImmediatePropagation();
|
|
2524
|
+
const open = Boolean(event.detail?.open);
|
|
2525
|
+
this.#reflectingOpen = true;
|
|
2526
|
+
this.setAttribute("open", String(open));
|
|
2527
|
+
this.#reflectingOpen = false;
|
|
2528
|
+
this.dispatchEvent(
|
|
2529
|
+
new CustomEvent("openchange", {
|
|
2530
|
+
detail: { open },
|
|
2531
|
+
bubbles: true,
|
|
2532
|
+
composed: true,
|
|
2533
|
+
}),
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
get collapsible() {
|
|
2538
|
+
const value = this.getAttribute("collapsible");
|
|
2539
|
+
return value === null || value !== "false";
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
set collapsible(value) {
|
|
2543
|
+
this.setAttribute("collapsible", String(Boolean(value)));
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
get open() {
|
|
2547
|
+
const value = this.getAttribute("open");
|
|
2548
|
+
return value === null || value !== "false";
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
set open(value) {
|
|
2552
|
+
this.setAttribute("open", String(Boolean(value)));
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
get value() {
|
|
2556
|
+
return { ...this.#readValue() };
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
set value(value) {
|
|
2560
|
+
const normalized = this.#reflectValue(value);
|
|
2561
|
+
this.#syncControls(normalized);
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
focus(options) {
|
|
2565
|
+
this.#colorControl?.focus(options);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
figLabDefineElement("propskit-color-point", PropskitColorPoint);
|
|
2569
|
+
|
|
2570
|
+
/**
|
|
2571
|
+
* Collapsible point-radius group composed from position and number controls.
|
|
2572
|
+
*
|
|
2573
|
+
* @attr {string} label - Passed to the internal fig-group name.
|
|
2574
|
+
* @attr {boolean|string} collapsible - Internal group collapsibility; defaults true.
|
|
2575
|
+
* @attr {boolean|string} open - Internal group expanded state; defaults true.
|
|
2576
|
+
* @attr {string} size - Passed to both internal PropsKit controls.
|
|
2577
|
+
* @attr {string} units - Passed to the internal position and radius controls.
|
|
2578
|
+
* @attr {string} value - JSON object with x, y, and radius.
|
|
2579
|
+
* @fires input - Composed event with { x, y, radius }.
|
|
2580
|
+
* @fires change - Composed event with { x, y, radius }.
|
|
2581
|
+
* @fires openchange - Composed event mirroring the internal fig-group.
|
|
2582
|
+
*/
|
|
2583
|
+
class PropskitPointRadius extends HTMLElement {
|
|
2584
|
+
static observedAttributes = [
|
|
2585
|
+
"label",
|
|
2586
|
+
"collapsible",
|
|
2587
|
+
"open",
|
|
2588
|
+
"size",
|
|
2589
|
+
"units",
|
|
2590
|
+
"value",
|
|
2591
|
+
];
|
|
2592
|
+
|
|
2593
|
+
#group = null;
|
|
2594
|
+
#positionControl = null;
|
|
2595
|
+
#radiusControl = null;
|
|
2596
|
+
#initialized = false;
|
|
2597
|
+
#reflectingValue = false;
|
|
2598
|
+
#reflectingOpen = false;
|
|
2599
|
+
#boundHandleInput = this.#handleControlEvent.bind(this, "input");
|
|
2600
|
+
#boundHandleChange = this.#handleControlEvent.bind(this, "change");
|
|
2601
|
+
#boundHandleOpenChange = this.#handleOpenChange.bind(this);
|
|
2602
|
+
|
|
2603
|
+
connectedCallback() {
|
|
2604
|
+
if (!this.#initialized) {
|
|
2605
|
+
this.#reflectValue(this.#readValue());
|
|
2606
|
+
this.#initialized = true;
|
|
2607
|
+
}
|
|
2608
|
+
if (!this.#group) this.#render();
|
|
2609
|
+
this.#syncGroupAttributes();
|
|
2610
|
+
this.#syncSize();
|
|
2611
|
+
this.#syncUnits();
|
|
2612
|
+
this.#syncControls(this.#readValue());
|
|
2613
|
+
this.removeEventListener("input", this.#boundHandleInput);
|
|
2614
|
+
this.addEventListener("input", this.#boundHandleInput);
|
|
2615
|
+
this.removeEventListener("change", this.#boundHandleChange);
|
|
2616
|
+
this.addEventListener("change", this.#boundHandleChange);
|
|
2617
|
+
this.removeEventListener("openchange", this.#boundHandleOpenChange);
|
|
2618
|
+
this.addEventListener("openchange", this.#boundHandleOpenChange);
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
disconnectedCallback() {
|
|
2622
|
+
this.removeEventListener("input", this.#boundHandleInput);
|
|
2623
|
+
this.removeEventListener("change", this.#boundHandleChange);
|
|
2624
|
+
this.removeEventListener("openchange", this.#boundHandleOpenChange);
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
2628
|
+
if (oldValue === newValue || !this.#initialized) return;
|
|
2629
|
+
if (name === "value") {
|
|
2630
|
+
if (!this.#reflectingValue) {
|
|
2631
|
+
const value = this.#reflectValue(this.#readValue());
|
|
2632
|
+
this.#syncControls(value);
|
|
2633
|
+
}
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
if (name === "size") {
|
|
2637
|
+
this.#syncSize();
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
if (name === "units") {
|
|
2641
|
+
this.#syncUnits();
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
this.#syncGroupAttributes();
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
#finiteNumber(value, fallback) {
|
|
2648
|
+
const number = Number(value);
|
|
2649
|
+
return Number.isFinite(number) ? number : fallback;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
#normalizeRadius(value) {
|
|
2653
|
+
if (typeof value === "string" && value.trim().endsWith("%")) {
|
|
2654
|
+
const number = Number.parseFloat(value);
|
|
2655
|
+
if (Number.isFinite(number)) return `${number}%`;
|
|
2656
|
+
}
|
|
2657
|
+
return this.#finiteNumber(value, 0);
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
#normalizeValue(value) {
|
|
2661
|
+
let source = value;
|
|
2662
|
+
if (typeof value === "string") {
|
|
2663
|
+
try {
|
|
2664
|
+
source = JSON.parse(value);
|
|
2665
|
+
} catch {
|
|
2666
|
+
source = null;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
2670
|
+
source = {};
|
|
2671
|
+
}
|
|
2672
|
+
return {
|
|
2673
|
+
x: this.#finiteNumber(source.x, 50),
|
|
2674
|
+
y: this.#finiteNumber(source.y, 50),
|
|
2675
|
+
radius: this.#normalizeRadius(source.radius),
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
#readValue() {
|
|
2680
|
+
return this.#normalizeValue(this.getAttribute("value"));
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
#reflectValue(value) {
|
|
2684
|
+
const normalized = this.#normalizeValue(value);
|
|
2685
|
+
const serialized = JSON.stringify(normalized);
|
|
2686
|
+
if (this.getAttribute("value") === serialized) return normalized;
|
|
2687
|
+
this.#reflectingValue = true;
|
|
2688
|
+
this.setAttribute("value", serialized);
|
|
2689
|
+
this.#reflectingValue = false;
|
|
2690
|
+
return normalized;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
#radiusParts(radius) {
|
|
2694
|
+
const percent =
|
|
2695
|
+
typeof radius === "string" && radius.trim().endsWith("%");
|
|
2696
|
+
return {
|
|
2697
|
+
value: percent ? Number.parseFloat(radius) : radius,
|
|
2698
|
+
units: percent ? "%" : "px",
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
#render() {
|
|
2703
|
+
const value = this.#readValue();
|
|
2704
|
+
const radiusValue = this.#radiusParts(value.radius);
|
|
2705
|
+
const group = document.createElement("fig-group");
|
|
2706
|
+
const position = document.createElement("propskit-position");
|
|
2707
|
+
const radius = document.createElement("propskit-number");
|
|
2708
|
+
group.setAttribute("compact", "");
|
|
2709
|
+
position.setAttribute("label", "Position");
|
|
2710
|
+
position.setAttribute("x", String(value.x));
|
|
2711
|
+
position.setAttribute("y", String(value.y));
|
|
2712
|
+
if (this.hasAttribute("units")) position.setAttribute("units", this.units);
|
|
2713
|
+
position.setAttribute("data-propskit-point-radius-control", "position");
|
|
2714
|
+
radius.setAttribute("label", "Radius");
|
|
2715
|
+
radius.setAttribute("value", String(radiusValue.value));
|
|
2716
|
+
if (this.units === "percent") radius.setAttribute("units", "%");
|
|
2717
|
+
radius.setAttribute("units-disallow", "");
|
|
2718
|
+
radius.setAttribute("min", "0");
|
|
2719
|
+
radius.setAttribute("precision", "2");
|
|
2720
|
+
radius.setAttribute("data-propskit-point-radius-control", "radius");
|
|
2721
|
+
this.#group = group;
|
|
2722
|
+
this.#positionControl = position;
|
|
2723
|
+
this.#radiusControl = radius;
|
|
2724
|
+
this.#syncGroupAttributes();
|
|
2725
|
+
this.#syncSize();
|
|
2726
|
+
group.append(position, radius);
|
|
2727
|
+
this.replaceChildren(group);
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
#syncGroupAttributes() {
|
|
2731
|
+
if (!this.#group) return;
|
|
2732
|
+
this.#group.setAttribute("compact", "");
|
|
2733
|
+
const label = this.getAttribute("label")?.trim();
|
|
2734
|
+
if (label) this.#group.setAttribute("name", label);
|
|
2735
|
+
else this.#group.removeAttribute("name");
|
|
2736
|
+
this.#group.toggleAttribute("collapsible", this.collapsible);
|
|
2737
|
+
if (this.collapsible) {
|
|
2738
|
+
this.#group.setAttribute("open", String(this.open));
|
|
2739
|
+
} else {
|
|
2740
|
+
this.#group.removeAttribute("open");
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
#syncSize() {
|
|
2745
|
+
const size = this.getAttribute("size");
|
|
2746
|
+
for (const control of [this.#positionControl, this.#radiusControl]) {
|
|
2747
|
+
if (!control) continue;
|
|
2748
|
+
if (size === null) control.removeAttribute("size");
|
|
2749
|
+
else control.setAttribute("size", size);
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
#syncUnits() {
|
|
2754
|
+
if (this.hasAttribute("units")) {
|
|
2755
|
+
this.#positionControl?.setAttribute("units", this.units);
|
|
2756
|
+
} else {
|
|
2757
|
+
this.#positionControl?.removeAttribute("units");
|
|
2758
|
+
}
|
|
2759
|
+
if (this.units === "percent") {
|
|
2760
|
+
this.#radiusControl?.setAttribute("units", "%");
|
|
2761
|
+
} else {
|
|
2762
|
+
this.#radiusControl?.removeAttribute("units");
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
#syncControls(value) {
|
|
2767
|
+
const normalized = this.#normalizeValue(value);
|
|
2768
|
+
if (this.#positionControl) {
|
|
2769
|
+
this.#positionControl.value = {
|
|
2770
|
+
x: normalized.x,
|
|
2771
|
+
y: normalized.y,
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
if (this.#radiusControl) {
|
|
2775
|
+
const radius = this.#radiusParts(normalized.radius);
|
|
2776
|
+
this.#radiusControl.value = radius.value;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
#radiusFromControl(control, fallback) {
|
|
2781
|
+
const fallbackValue = this.#radiusParts(fallback).value;
|
|
2782
|
+
const value = this.#finiteNumber(control.value, fallbackValue);
|
|
2783
|
+
const units =
|
|
2784
|
+
control.querySelector("fig-input-number")?.getAttribute("units") ||
|
|
2785
|
+
control.getAttribute("units");
|
|
2786
|
+
return units === "%" ? `${value}%` : value;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
#handleControlEvent(type, event) {
|
|
2790
|
+
if (event.target === this) return;
|
|
2791
|
+
const control = event.target?.closest?.(
|
|
2792
|
+
"[data-propskit-point-radius-control]",
|
|
2793
|
+
);
|
|
2794
|
+
if (!control || !this.contains(control)) return;
|
|
2795
|
+
event.stopImmediatePropagation();
|
|
2796
|
+
const value = this.value;
|
|
2797
|
+
const kind = control.getAttribute("data-propskit-point-radius-control");
|
|
2798
|
+
if (kind === "position") {
|
|
2799
|
+
value.x = this.#finiteNumber(control.value?.x, value.x);
|
|
2800
|
+
value.y = this.#finiteNumber(control.value?.y, value.y);
|
|
2801
|
+
} else if (kind === "radius") {
|
|
2802
|
+
value.radius = this.#radiusFromControl(control, value.radius);
|
|
2803
|
+
} else {
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
const detail = this.#reflectValue(value);
|
|
2807
|
+
this.dispatchEvent(
|
|
2808
|
+
new CustomEvent(type, {
|
|
2809
|
+
detail: {
|
|
2810
|
+
...detail,
|
|
2811
|
+
radius: this.#radiusParts(detail.radius).value,
|
|
2812
|
+
units: this.units,
|
|
2813
|
+
},
|
|
2814
|
+
bubbles: true,
|
|
2815
|
+
cancelable: true,
|
|
2816
|
+
composed: true,
|
|
2817
|
+
}),
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
#handleOpenChange(event) {
|
|
2822
|
+
if (event.target !== this.#group || this.#reflectingOpen) return;
|
|
2823
|
+
event.stopImmediatePropagation();
|
|
2824
|
+
const open = Boolean(event.detail?.open);
|
|
2825
|
+
this.#reflectingOpen = true;
|
|
2826
|
+
this.setAttribute("open", String(open));
|
|
2827
|
+
this.#reflectingOpen = false;
|
|
2828
|
+
this.dispatchEvent(
|
|
2829
|
+
new CustomEvent("openchange", {
|
|
2830
|
+
detail: { open },
|
|
2831
|
+
bubbles: true,
|
|
2832
|
+
composed: true,
|
|
2833
|
+
}),
|
|
2834
|
+
);
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
get collapsible() {
|
|
2838
|
+
const value = this.getAttribute("collapsible");
|
|
2839
|
+
return value === null || value !== "false";
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
set collapsible(value) {
|
|
2843
|
+
this.setAttribute("collapsible", String(Boolean(value)));
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
get open() {
|
|
2847
|
+
const value = this.getAttribute("open");
|
|
2848
|
+
return value === null || value !== "false";
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
set open(value) {
|
|
2852
|
+
this.setAttribute("open", String(Boolean(value)));
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
get units() {
|
|
2856
|
+
return this.getAttribute("units")?.trim().toLowerCase() === "percent"
|
|
2857
|
+
? "percent"
|
|
2858
|
+
: "none";
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
set units(value) {
|
|
2862
|
+
this.setAttribute(
|
|
2863
|
+
"units",
|
|
2864
|
+
String(value).trim().toLowerCase() === "none" ? "none" : "percent",
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
get value() {
|
|
2869
|
+
return { ...this.#readValue() };
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
set value(value) {
|
|
2873
|
+
const normalized = this.#reflectValue(value);
|
|
2874
|
+
this.#syncControls(normalized);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
focus(options) {
|
|
2878
|
+
this.#positionControl?.focus(options);
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
figLabDefineElement("propskit-point-radius", PropskitPointRadius);
|
|
2882
|
+
|
|
2883
|
+
/**
|
|
2884
|
+
* Collapsible point-radius-angle group composed from PropsKit controls.
|
|
2885
|
+
*
|
|
2886
|
+
* @attr {string} label - Passed to the internal fig-group name.
|
|
2887
|
+
* @attr {boolean|string} collapsible - Internal group collapsibility; defaults true.
|
|
2888
|
+
* @attr {boolean|string} open - Internal group expanded state; defaults true.
|
|
2889
|
+
* @attr {string} size - Passed to every internal PropsKit control.
|
|
2890
|
+
* @attr {string} units - Passed to the internal position and radius controls.
|
|
2891
|
+
* @attr {string} value - JSON object with x, y, radius, and angle.
|
|
2892
|
+
* @fires input - Composed event with { x, y, radius, angle }.
|
|
2893
|
+
* @fires change - Composed event with { x, y, radius, angle }.
|
|
2894
|
+
* @fires openchange - Composed event mirroring the internal fig-group.
|
|
2895
|
+
*/
|
|
2896
|
+
class PropskitPointRadiusAngle extends HTMLElement {
|
|
2897
|
+
static observedAttributes = [
|
|
2898
|
+
"label",
|
|
2899
|
+
"collapsible",
|
|
2900
|
+
"open",
|
|
2901
|
+
"size",
|
|
2902
|
+
"units",
|
|
2903
|
+
"value",
|
|
2904
|
+
];
|
|
2905
|
+
|
|
2906
|
+
#group = null;
|
|
2907
|
+
#positionControl = null;
|
|
2908
|
+
#radiusControl = null;
|
|
2909
|
+
#angleControl = null;
|
|
2910
|
+
#initialized = false;
|
|
2911
|
+
#reflectingValue = false;
|
|
2912
|
+
#reflectingOpen = false;
|
|
2913
|
+
#boundHandleInput = this.#handleControlEvent.bind(this, "input");
|
|
2914
|
+
#boundHandleChange = this.#handleControlEvent.bind(this, "change");
|
|
2915
|
+
#boundHandleOpenChange = this.#handleOpenChange.bind(this);
|
|
2916
|
+
|
|
2917
|
+
connectedCallback() {
|
|
2918
|
+
if (!this.#initialized) {
|
|
2919
|
+
this.#reflectValue(this.#readValue());
|
|
2920
|
+
this.#initialized = true;
|
|
2921
|
+
}
|
|
2922
|
+
if (!this.#group) this.#render();
|
|
2923
|
+
this.#syncGroupAttributes();
|
|
2924
|
+
this.#syncSize();
|
|
2925
|
+
this.#syncUnits();
|
|
2926
|
+
this.#syncControls(this.#readValue());
|
|
2927
|
+
this.removeEventListener("input", this.#boundHandleInput);
|
|
2928
|
+
this.addEventListener("input", this.#boundHandleInput);
|
|
2929
|
+
this.removeEventListener("change", this.#boundHandleChange);
|
|
2930
|
+
this.addEventListener("change", this.#boundHandleChange);
|
|
2931
|
+
this.removeEventListener("openchange", this.#boundHandleOpenChange);
|
|
2932
|
+
this.addEventListener("openchange", this.#boundHandleOpenChange);
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
disconnectedCallback() {
|
|
2936
|
+
this.removeEventListener("input", this.#boundHandleInput);
|
|
2937
|
+
this.removeEventListener("change", this.#boundHandleChange);
|
|
2938
|
+
this.removeEventListener("openchange", this.#boundHandleOpenChange);
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
2942
|
+
if (oldValue === newValue || !this.#initialized) return;
|
|
2943
|
+
if (name === "value") {
|
|
2944
|
+
if (!this.#reflectingValue) {
|
|
2945
|
+
const value = this.#reflectValue(this.#readValue());
|
|
2946
|
+
this.#syncControls(value);
|
|
2947
|
+
}
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
if (name === "size") {
|
|
2951
|
+
this.#syncSize();
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
if (name === "units") {
|
|
2955
|
+
this.#syncUnits();
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
this.#syncGroupAttributes();
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
#finiteNumber(value, fallback) {
|
|
2962
|
+
const number = Number(value);
|
|
2963
|
+
return Number.isFinite(number) ? number : fallback;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
#normalizeRadius(value) {
|
|
2967
|
+
if (typeof value === "string" && value.trim().endsWith("%")) {
|
|
2968
|
+
const number = Number.parseFloat(value);
|
|
2969
|
+
if (Number.isFinite(number)) return `${number}%`;
|
|
2970
|
+
}
|
|
2971
|
+
return this.#finiteNumber(value, 0);
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
#normalizeValue(value) {
|
|
2975
|
+
let source = value;
|
|
2976
|
+
if (typeof value === "string") {
|
|
2977
|
+
try {
|
|
2978
|
+
source = JSON.parse(value);
|
|
2979
|
+
} catch {
|
|
2980
|
+
source = null;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
2984
|
+
source = {};
|
|
2985
|
+
}
|
|
2986
|
+
return {
|
|
2987
|
+
x: this.#finiteNumber(source.x, 50),
|
|
2988
|
+
y: this.#finiteNumber(source.y, 50),
|
|
2989
|
+
radius: this.#normalizeRadius(source.radius),
|
|
2990
|
+
angle: this.#finiteNumber(source.angle, 0),
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
#readValue() {
|
|
2995
|
+
return this.#normalizeValue(this.getAttribute("value"));
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
#reflectValue(value) {
|
|
2999
|
+
const normalized = this.#normalizeValue(value);
|
|
3000
|
+
const serialized = JSON.stringify(normalized);
|
|
3001
|
+
if (this.getAttribute("value") === serialized) return normalized;
|
|
3002
|
+
this.#reflectingValue = true;
|
|
3003
|
+
this.setAttribute("value", serialized);
|
|
3004
|
+
this.#reflectingValue = false;
|
|
3005
|
+
return normalized;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
#radiusParts(radius) {
|
|
3009
|
+
const percent =
|
|
3010
|
+
typeof radius === "string" && radius.trim().endsWith("%");
|
|
3011
|
+
return {
|
|
3012
|
+
value: percent ? Number.parseFloat(radius) : radius,
|
|
3013
|
+
units: percent ? "%" : "px",
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
#render() {
|
|
3018
|
+
const value = this.#readValue();
|
|
3019
|
+
const radiusValue = this.#radiusParts(value.radius);
|
|
3020
|
+
const group = document.createElement("fig-group");
|
|
3021
|
+
const position = document.createElement("propskit-position");
|
|
3022
|
+
const radius = document.createElement("propskit-number");
|
|
3023
|
+
const angle = document.createElement("propskit-number");
|
|
3024
|
+
group.setAttribute("compact", "");
|
|
3025
|
+
position.setAttribute("label", "Position");
|
|
3026
|
+
position.setAttribute("x", String(value.x));
|
|
3027
|
+
position.setAttribute("y", String(value.y));
|
|
3028
|
+
if (this.hasAttribute("units")) position.setAttribute("units", this.units);
|
|
3029
|
+
position.setAttribute(
|
|
3030
|
+
"data-propskit-point-radius-angle-control",
|
|
3031
|
+
"position",
|
|
3032
|
+
);
|
|
3033
|
+
radius.setAttribute("label", "Radius");
|
|
3034
|
+
radius.setAttribute("value", String(radiusValue.value));
|
|
3035
|
+
if (this.units === "percent") radius.setAttribute("units", "%");
|
|
3036
|
+
radius.setAttribute("units-disallow", "");
|
|
3037
|
+
radius.setAttribute("min", "0");
|
|
3038
|
+
radius.setAttribute("precision", "2");
|
|
3039
|
+
radius.setAttribute("data-propskit-point-radius-angle-control", "radius");
|
|
3040
|
+
angle.setAttribute("label", "Angle");
|
|
3041
|
+
angle.setAttribute("value", String(value.angle));
|
|
3042
|
+
angle.setAttribute("units", "°");
|
|
3043
|
+
angle.setAttribute("units-disallow", "");
|
|
3044
|
+
angle.setAttribute("precision", "1");
|
|
3045
|
+
angle.setAttribute("data-propskit-point-radius-angle-control", "angle");
|
|
3046
|
+
this.#group = group;
|
|
3047
|
+
this.#positionControl = position;
|
|
3048
|
+
this.#radiusControl = radius;
|
|
3049
|
+
this.#angleControl = angle;
|
|
3050
|
+
this.#syncGroupAttributes();
|
|
3051
|
+
this.#syncSize();
|
|
3052
|
+
group.append(position, radius, angle);
|
|
3053
|
+
this.replaceChildren(group);
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
#syncGroupAttributes() {
|
|
3057
|
+
if (!this.#group) return;
|
|
3058
|
+
this.#group.setAttribute("compact", "");
|
|
3059
|
+
const label = this.getAttribute("label")?.trim();
|
|
3060
|
+
if (label) this.#group.setAttribute("name", label);
|
|
3061
|
+
else this.#group.removeAttribute("name");
|
|
3062
|
+
this.#group.toggleAttribute("collapsible", this.collapsible);
|
|
3063
|
+
if (this.collapsible) {
|
|
3064
|
+
this.#group.setAttribute("open", String(this.open));
|
|
3065
|
+
} else {
|
|
3066
|
+
this.#group.removeAttribute("open");
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
#syncSize() {
|
|
3071
|
+
const size = this.getAttribute("size");
|
|
3072
|
+
for (const control of [
|
|
3073
|
+
this.#positionControl,
|
|
3074
|
+
this.#radiusControl,
|
|
3075
|
+
this.#angleControl,
|
|
3076
|
+
]) {
|
|
3077
|
+
if (!control) continue;
|
|
3078
|
+
if (size === null) control.removeAttribute("size");
|
|
3079
|
+
else control.setAttribute("size", size);
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
#syncUnits() {
|
|
3084
|
+
if (this.hasAttribute("units")) {
|
|
3085
|
+
this.#positionControl?.setAttribute("units", this.units);
|
|
3086
|
+
} else {
|
|
3087
|
+
this.#positionControl?.removeAttribute("units");
|
|
3088
|
+
}
|
|
3089
|
+
if (this.units === "percent") {
|
|
3090
|
+
this.#radiusControl?.setAttribute("units", "%");
|
|
3091
|
+
} else {
|
|
3092
|
+
this.#radiusControl?.removeAttribute("units");
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
#syncControls(value) {
|
|
3097
|
+
const normalized = this.#normalizeValue(value);
|
|
3098
|
+
if (this.#positionControl) {
|
|
3099
|
+
this.#positionControl.value = {
|
|
3100
|
+
x: normalized.x,
|
|
3101
|
+
y: normalized.y,
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
if (this.#radiusControl) {
|
|
3105
|
+
const radius = this.#radiusParts(normalized.radius);
|
|
3106
|
+
this.#radiusControl.value = radius.value;
|
|
3107
|
+
}
|
|
3108
|
+
if (this.#angleControl) {
|
|
3109
|
+
this.#angleControl.value = normalized.angle;
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
#radiusFromControl(control, fallback) {
|
|
3114
|
+
const fallbackValue = this.#radiusParts(fallback).value;
|
|
3115
|
+
const value = this.#finiteNumber(control.value, fallbackValue);
|
|
3116
|
+
const units =
|
|
3117
|
+
control.querySelector("fig-input-number")?.getAttribute("units") ||
|
|
3118
|
+
control.getAttribute("units");
|
|
3119
|
+
return units === "%" ? `${value}%` : value;
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
#handleControlEvent(type, event) {
|
|
3123
|
+
if (event.target === this) return;
|
|
3124
|
+
const control = event.target?.closest?.(
|
|
3125
|
+
"[data-propskit-point-radius-angle-control]",
|
|
3126
|
+
);
|
|
3127
|
+
if (!control || !this.contains(control)) return;
|
|
3128
|
+
event.stopImmediatePropagation();
|
|
3129
|
+
const value = this.value;
|
|
3130
|
+
const kind = control.getAttribute(
|
|
3131
|
+
"data-propskit-point-radius-angle-control",
|
|
3132
|
+
);
|
|
3133
|
+
if (kind === "position") {
|
|
3134
|
+
value.x = this.#finiteNumber(control.value?.x, value.x);
|
|
3135
|
+
value.y = this.#finiteNumber(control.value?.y, value.y);
|
|
3136
|
+
} else if (kind === "radius") {
|
|
3137
|
+
value.radius = this.#radiusFromControl(control, value.radius);
|
|
3138
|
+
} else if (kind === "angle") {
|
|
3139
|
+
value.angle = this.#finiteNumber(control.value, value.angle);
|
|
3140
|
+
} else {
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
const detail = this.#reflectValue(value);
|
|
3144
|
+
this.dispatchEvent(
|
|
3145
|
+
new CustomEvent(type, {
|
|
3146
|
+
detail: {
|
|
3147
|
+
...detail,
|
|
3148
|
+
radius: this.#radiusParts(detail.radius).value,
|
|
3149
|
+
units: this.units,
|
|
3150
|
+
},
|
|
3151
|
+
bubbles: true,
|
|
3152
|
+
cancelable: true,
|
|
3153
|
+
composed: true,
|
|
3154
|
+
}),
|
|
3155
|
+
);
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
#handleOpenChange(event) {
|
|
3159
|
+
if (event.target !== this.#group || this.#reflectingOpen) return;
|
|
3160
|
+
event.stopImmediatePropagation();
|
|
3161
|
+
const open = Boolean(event.detail?.open);
|
|
3162
|
+
this.#reflectingOpen = true;
|
|
3163
|
+
this.setAttribute("open", String(open));
|
|
3164
|
+
this.#reflectingOpen = false;
|
|
3165
|
+
this.dispatchEvent(
|
|
3166
|
+
new CustomEvent("openchange", {
|
|
3167
|
+
detail: { open },
|
|
3168
|
+
bubbles: true,
|
|
3169
|
+
composed: true,
|
|
3170
|
+
}),
|
|
3171
|
+
);
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
get collapsible() {
|
|
3175
|
+
const value = this.getAttribute("collapsible");
|
|
3176
|
+
return value === null || value !== "false";
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
set collapsible(value) {
|
|
3180
|
+
this.setAttribute("collapsible", String(Boolean(value)));
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
get open() {
|
|
3184
|
+
const value = this.getAttribute("open");
|
|
3185
|
+
return value === null || value !== "false";
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
set open(value) {
|
|
3189
|
+
this.setAttribute("open", String(Boolean(value)));
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
get units() {
|
|
3193
|
+
return this.getAttribute("units")?.trim().toLowerCase() === "percent"
|
|
3194
|
+
? "percent"
|
|
3195
|
+
: "none";
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
set units(value) {
|
|
3199
|
+
this.setAttribute(
|
|
3200
|
+
"units",
|
|
3201
|
+
String(value).trim().toLowerCase() === "none" ? "none" : "percent",
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
get value() {
|
|
3206
|
+
return { ...this.#readValue() };
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
set value(value) {
|
|
3210
|
+
const normalized = this.#reflectValue(value);
|
|
3211
|
+
this.#syncControls(normalized);
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
focus(options) {
|
|
3215
|
+
this.#positionControl?.focus(options);
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
figLabDefineElement(
|
|
3219
|
+
"propskit-point-radius-angle",
|
|
3220
|
+
PropskitPointRadiusAngle,
|
|
3221
|
+
);
|
|
3222
|
+
|
|
3223
|
+
/**
|
|
3224
|
+
* Collapsible point-point group composed from two position controls.
|
|
3225
|
+
*
|
|
3226
|
+
* @attr {string} label - Passed to the internal fig-group name.
|
|
3227
|
+
* @attr {boolean|string} collapsible - Internal group collapsibility; defaults true.
|
|
3228
|
+
* @attr {boolean|string} open - Internal group expanded state; defaults true.
|
|
3229
|
+
* @attr {string} size - Passed to both internal position controls.
|
|
3230
|
+
* @attr {string} units - Passed to both internal position controls.
|
|
3231
|
+
* @attr {string} value - JSON object with x, y, x2, and y2.
|
|
3232
|
+
* @fires input - Composed event with { x, y, x2, y2 }.
|
|
3233
|
+
* @fires change - Composed event with { x, y, x2, y2 }.
|
|
3234
|
+
* @fires openchange - Composed event mirroring the internal fig-group.
|
|
3235
|
+
*/
|
|
3236
|
+
class PropskitPointPoint extends HTMLElement {
|
|
3237
|
+
static observedAttributes = [
|
|
3238
|
+
"label",
|
|
3239
|
+
"collapsible",
|
|
3240
|
+
"open",
|
|
3241
|
+
"size",
|
|
3242
|
+
"units",
|
|
3243
|
+
"value",
|
|
3244
|
+
];
|
|
3245
|
+
|
|
3246
|
+
#group = null;
|
|
3247
|
+
#startControl = null;
|
|
3248
|
+
#endControl = null;
|
|
3249
|
+
#initialized = false;
|
|
3250
|
+
#reflectingValue = false;
|
|
3251
|
+
#reflectingOpen = false;
|
|
3252
|
+
#boundHandleInput = this.#handleControlEvent.bind(this, "input");
|
|
3253
|
+
#boundHandleChange = this.#handleControlEvent.bind(this, "change");
|
|
3254
|
+
#boundHandleOpenChange = this.#handleOpenChange.bind(this);
|
|
3255
|
+
|
|
3256
|
+
connectedCallback() {
|
|
3257
|
+
if (!this.#initialized) {
|
|
3258
|
+
this.#reflectValue(this.#readValue());
|
|
3259
|
+
this.#initialized = true;
|
|
3260
|
+
}
|
|
3261
|
+
if (!this.#group) this.#render();
|
|
3262
|
+
this.#syncGroupAttributes();
|
|
3263
|
+
this.#syncSize();
|
|
3264
|
+
this.#syncUnits();
|
|
3265
|
+
this.#syncControls(this.#readValue());
|
|
3266
|
+
this.removeEventListener("input", this.#boundHandleInput);
|
|
3267
|
+
this.addEventListener("input", this.#boundHandleInput);
|
|
3268
|
+
this.removeEventListener("change", this.#boundHandleChange);
|
|
3269
|
+
this.addEventListener("change", this.#boundHandleChange);
|
|
3270
|
+
this.removeEventListener("openchange", this.#boundHandleOpenChange);
|
|
3271
|
+
this.addEventListener("openchange", this.#boundHandleOpenChange);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
disconnectedCallback() {
|
|
3275
|
+
this.removeEventListener("input", this.#boundHandleInput);
|
|
3276
|
+
this.removeEventListener("change", this.#boundHandleChange);
|
|
3277
|
+
this.removeEventListener("openchange", this.#boundHandleOpenChange);
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
3281
|
+
if (oldValue === newValue || !this.#initialized) return;
|
|
3282
|
+
if (name === "value") {
|
|
3283
|
+
if (!this.#reflectingValue) {
|
|
3284
|
+
const value = this.#reflectValue(this.#readValue());
|
|
3285
|
+
this.#syncControls(value);
|
|
3286
|
+
}
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
if (name === "size") {
|
|
3290
|
+
this.#syncSize();
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
if (name === "units") {
|
|
3294
|
+
this.#syncUnits();
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
this.#syncGroupAttributes();
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
#finiteNumber(value, fallback) {
|
|
3301
|
+
const number = Number(value);
|
|
3302
|
+
return Number.isFinite(number) ? number : fallback;
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
#normalizeValue(value) {
|
|
3306
|
+
let source = value;
|
|
3307
|
+
if (typeof value === "string") {
|
|
3308
|
+
try {
|
|
3309
|
+
source = JSON.parse(value);
|
|
3310
|
+
} catch {
|
|
3311
|
+
source = null;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
3315
|
+
source = {};
|
|
3316
|
+
}
|
|
3317
|
+
return {
|
|
3318
|
+
x: this.#finiteNumber(source.x, 50),
|
|
3319
|
+
y: this.#finiteNumber(source.y, 50),
|
|
3320
|
+
x2: this.#finiteNumber(source.x2, 75),
|
|
3321
|
+
y2: this.#finiteNumber(source.y2, 75),
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
#readValue() {
|
|
3326
|
+
return this.#normalizeValue(this.getAttribute("value"));
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
#reflectValue(value) {
|
|
3330
|
+
const normalized = this.#normalizeValue(value);
|
|
3331
|
+
const serialized = JSON.stringify(normalized);
|
|
3332
|
+
if (this.getAttribute("value") === serialized) return normalized;
|
|
3333
|
+
this.#reflectingValue = true;
|
|
3334
|
+
this.setAttribute("value", serialized);
|
|
3335
|
+
this.#reflectingValue = false;
|
|
3336
|
+
return normalized;
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
#createPosition(label, role, x, y) {
|
|
3340
|
+
const control = document.createElement("propskit-position");
|
|
3341
|
+
control.setAttribute("label", label);
|
|
3342
|
+
control.setAttribute("x", String(x));
|
|
3343
|
+
control.setAttribute("y", String(y));
|
|
3344
|
+
if (this.hasAttribute("units")) control.setAttribute("units", this.units);
|
|
3345
|
+
control.setAttribute("data-propskit-point-point-control", role);
|
|
3346
|
+
return control;
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
#render() {
|
|
3350
|
+
const value = this.#readValue();
|
|
3351
|
+
const group = document.createElement("fig-group");
|
|
3352
|
+
const start = this.#createPosition("Start", "start", value.x, value.y);
|
|
3353
|
+
const end = this.#createPosition("End", "end", value.x2, value.y2);
|
|
3354
|
+
group.setAttribute("compact", "");
|
|
3355
|
+
this.#group = group;
|
|
3356
|
+
this.#startControl = start;
|
|
3357
|
+
this.#endControl = end;
|
|
3358
|
+
this.#syncGroupAttributes();
|
|
3359
|
+
this.#syncSize();
|
|
3360
|
+
group.append(start, end);
|
|
3361
|
+
this.replaceChildren(group);
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
#syncGroupAttributes() {
|
|
3365
|
+
if (!this.#group) return;
|
|
3366
|
+
this.#group.setAttribute("compact", "");
|
|
3367
|
+
const label = this.getAttribute("label")?.trim();
|
|
3368
|
+
if (label) this.#group.setAttribute("name", label);
|
|
3369
|
+
else this.#group.removeAttribute("name");
|
|
3370
|
+
this.#group.toggleAttribute("collapsible", this.collapsible);
|
|
3371
|
+
if (this.collapsible) {
|
|
3372
|
+
this.#group.setAttribute("open", String(this.open));
|
|
3373
|
+
} else {
|
|
3374
|
+
this.#group.removeAttribute("open");
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
#syncSize() {
|
|
3379
|
+
const size = this.getAttribute("size");
|
|
3380
|
+
for (const control of [this.#startControl, this.#endControl]) {
|
|
3381
|
+
if (!control) continue;
|
|
3382
|
+
if (size === null) control.removeAttribute("size");
|
|
3383
|
+
else control.setAttribute("size", size);
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
#syncUnits() {
|
|
3388
|
+
for (const control of [this.#startControl, this.#endControl]) {
|
|
3389
|
+
if (!control) continue;
|
|
3390
|
+
if (this.hasAttribute("units")) control.setAttribute("units", this.units);
|
|
3391
|
+
else control.removeAttribute("units");
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
#syncControls(value) {
|
|
3396
|
+
const normalized = this.#normalizeValue(value);
|
|
3397
|
+
if (this.#startControl) {
|
|
3398
|
+
this.#startControl.value = {
|
|
3399
|
+
x: normalized.x,
|
|
3400
|
+
y: normalized.y,
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
if (this.#endControl) {
|
|
3404
|
+
this.#endControl.value = {
|
|
3405
|
+
x: normalized.x2,
|
|
3406
|
+
y: normalized.y2,
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
#handleControlEvent(type, event) {
|
|
3412
|
+
if (event.target === this) return;
|
|
3413
|
+
const control = event.target?.closest?.(
|
|
3414
|
+
"[data-propskit-point-point-control]",
|
|
3415
|
+
);
|
|
3416
|
+
if (!control || !this.contains(control)) return;
|
|
3417
|
+
event.stopImmediatePropagation();
|
|
3418
|
+
const value = this.value;
|
|
3419
|
+
const point = control.value;
|
|
3420
|
+
const role = control.getAttribute("data-propskit-point-point-control");
|
|
3421
|
+
if (role === "start") {
|
|
3422
|
+
value.x = this.#finiteNumber(point?.x, value.x);
|
|
3423
|
+
value.y = this.#finiteNumber(point?.y, value.y);
|
|
3424
|
+
} else if (role === "end") {
|
|
3425
|
+
value.x2 = this.#finiteNumber(point?.x, value.x2);
|
|
3426
|
+
value.y2 = this.#finiteNumber(point?.y, value.y2);
|
|
3427
|
+
} else {
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
const detail = this.#reflectValue(value);
|
|
3431
|
+
this.dispatchEvent(
|
|
3432
|
+
new CustomEvent(type, {
|
|
3433
|
+
detail: { ...detail, units: this.units },
|
|
3434
|
+
bubbles: true,
|
|
3435
|
+
cancelable: true,
|
|
3436
|
+
composed: true,
|
|
3437
|
+
}),
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
#handleOpenChange(event) {
|
|
3442
|
+
if (event.target !== this.#group || this.#reflectingOpen) return;
|
|
3443
|
+
event.stopImmediatePropagation();
|
|
3444
|
+
const open = Boolean(event.detail?.open);
|
|
3445
|
+
this.#reflectingOpen = true;
|
|
3446
|
+
this.setAttribute("open", String(open));
|
|
3447
|
+
this.#reflectingOpen = false;
|
|
3448
|
+
this.dispatchEvent(
|
|
3449
|
+
new CustomEvent("openchange", {
|
|
3450
|
+
detail: { open },
|
|
3451
|
+
bubbles: true,
|
|
3452
|
+
composed: true,
|
|
3453
|
+
}),
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
get collapsible() {
|
|
3458
|
+
const value = this.getAttribute("collapsible");
|
|
3459
|
+
return value === null || value !== "false";
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
set collapsible(value) {
|
|
3463
|
+
this.setAttribute("collapsible", String(Boolean(value)));
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
get open() {
|
|
3467
|
+
const value = this.getAttribute("open");
|
|
3468
|
+
return value === null || value !== "false";
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
set open(value) {
|
|
3472
|
+
this.setAttribute("open", String(Boolean(value)));
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
get units() {
|
|
3476
|
+
return this.getAttribute("units")?.trim().toLowerCase() === "percent"
|
|
3477
|
+
? "percent"
|
|
3478
|
+
: "none";
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
set units(value) {
|
|
3482
|
+
this.setAttribute(
|
|
3483
|
+
"units",
|
|
3484
|
+
String(value).trim().toLowerCase() === "none" ? "none" : "percent",
|
|
3485
|
+
);
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
get value() {
|
|
3489
|
+
return { ...this.#readValue() };
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
set value(value) {
|
|
3493
|
+
const normalized = this.#reflectValue(value);
|
|
3494
|
+
this.#syncControls(normalized);
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
focus(options) {
|
|
3498
|
+
this.#startControl?.focus(options);
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
figLabDefineElement("propskit-point-point", PropskitPointPoint);
|
|
3502
|
+
|
|
2043
3503
|
/* Collapsible property group — always collapsible (no collapsible attr). */
|
|
2044
3504
|
class PropskitGroup extends HTMLElement {
|
|
2045
3505
|
static observedAttributes = ["name", "open", "show-reset"];
|
|
@@ -2048,6 +3508,7 @@ class PropskitGroup extends HTMLElement {
|
|
|
2048
3508
|
"propskit-color",
|
|
2049
3509
|
"propskit-gradient",
|
|
2050
3510
|
"propskit-number",
|
|
3511
|
+
"propskit-position",
|
|
2051
3512
|
"propskit-select",
|
|
2052
3513
|
"propskit-slider",
|
|
2053
3514
|
"propskit-switch",
|
|
@@ -2156,7 +3617,7 @@ class PropskitGroup extends HTMLElement {
|
|
|
2156
3617
|
childList: true,
|
|
2157
3618
|
subtree: true,
|
|
2158
3619
|
attributes: true,
|
|
2159
|
-
attributeFilter: ["value", "checked", "default"],
|
|
3620
|
+
attributeFilter: ["value", "checked", "default", "x", "y"],
|
|
2160
3621
|
});
|
|
2161
3622
|
}
|
|
2162
3623
|
|
|
@@ -3437,6 +4898,14 @@ class FigCanvasControl extends HTMLElement {
|
|
|
3437
4898
|
if (typeof v.angle === "number") this.#angle = v.angle;
|
|
3438
4899
|
if (typeof v.x2 === "number") this.#x2 = v.x2;
|
|
3439
4900
|
if (typeof v.y2 === "number") this.#y2 = v.y2;
|
|
4901
|
+
if (
|
|
4902
|
+
this.#type === "color" &&
|
|
4903
|
+
typeof v.color === "string" &&
|
|
4904
|
+
v.color.trim() &&
|
|
4905
|
+
this.getAttribute("color") !== v.color.trim()
|
|
4906
|
+
) {
|
|
4907
|
+
this.setAttribute("color", v.color.trim());
|
|
4908
|
+
}
|
|
3440
4909
|
} catch {
|
|
3441
4910
|
/* ignore */
|
|
3442
4911
|
}
|
|
@@ -6239,7 +7708,12 @@ class FigReorder extends HTMLElement {
|
|
|
6239
7708
|
"fig-input-angle",
|
|
6240
7709
|
"fig-input-joystick",
|
|
6241
7710
|
"fig-canvas-control",
|
|
7711
|
+
"propskit-color-point",
|
|
6242
7712
|
"propskit-number",
|
|
7713
|
+
"propskit-point-point",
|
|
7714
|
+
"propskit-point-radius",
|
|
7715
|
+
"propskit-point-radius-angle",
|
|
7716
|
+
"propskit-position",
|
|
6243
7717
|
"propskit-slider",
|
|
6244
7718
|
"propskit-oscillator",
|
|
6245
7719
|
];
|