@rogieking/figui3 11.4.1 → 11.5.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/.cursor/skills/fig-editor/SKILL.md +8 -6
- package/.cursor/skills/fig-editor/components.md +55 -0
- package/.cursor/skills/fig-editor/reference.md +25 -0
- package/.cursor/skills/figui3/components.md +1 -0
- package/.cursor/skills/figui3/react.md +2 -0
- package/.cursor/skills/figui3/reference.md +2 -0
- package/CHANGELOG.md +27 -0
- package/README.md +43 -0
- package/components.css +60 -11
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +3 -3
- package/dist/fig-property.css +1 -0
- package/dist/fig-property.js +1 -0
- package/dist/fig.css +1 -1
- package/dist/fig.js +2 -2
- package/fig-editor.css +48 -24
- package/fig-editor.js +207 -81
- package/fig-property.css +116 -0
- package/fig-property.js +220 -0
- package/fig.js +9 -2
- package/package.json +9 -2
package/fig-property.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/* Copyright (c) 2026 Rogie King. PolyForm Shield 1.0.0. See LICENSE. */
|
|
2
|
+
import "./fig.js";
|
|
3
|
+
|
|
4
|
+
let figPropertyId = 0;
|
|
5
|
+
|
|
6
|
+
function defineElement(name, constructor) {
|
|
7
|
+
if (!customElements.get(name)) customElements.define(name, constructor);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createIcon(name) {
|
|
11
|
+
const icon = document.createElement("fig-icon");
|
|
12
|
+
icon.setAttribute("name", name);
|
|
13
|
+
icon.setAttribute("size", "small");
|
|
14
|
+
icon.setAttribute("aria-hidden", "true");
|
|
15
|
+
return icon;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class FigProperty extends HTMLElement {
|
|
19
|
+
static observedAttributes = [
|
|
20
|
+
"label",
|
|
21
|
+
"leading",
|
|
22
|
+
"trailing",
|
|
23
|
+
"show-leading",
|
|
24
|
+
"show-trailing",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
#observer = null;
|
|
28
|
+
#labelElement = null;
|
|
29
|
+
#associatedInput = null;
|
|
30
|
+
#syncQueued = false;
|
|
31
|
+
|
|
32
|
+
connectedCallback() {
|
|
33
|
+
this.#sync();
|
|
34
|
+
this.#observer = new MutationObserver(() => this.#queueSync());
|
|
35
|
+
this.#observer.observe(this, {
|
|
36
|
+
childList: true,
|
|
37
|
+
attributes: true,
|
|
38
|
+
attributeFilter: ["slot"],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
disconnectedCallback() {
|
|
43
|
+
this.#observer?.disconnect();
|
|
44
|
+
this.#observer = null;
|
|
45
|
+
this.#clearLabelAssociation();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
attributeChangedCallback() {
|
|
49
|
+
if (this.isConnected) this.#queueSync();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get input() {
|
|
53
|
+
return this.querySelector(':scope > [slot="input"]');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#queueSync() {
|
|
57
|
+
if (this.#syncQueued) return;
|
|
58
|
+
this.#syncQueued = true;
|
|
59
|
+
queueMicrotask(() => {
|
|
60
|
+
this.#syncQueued = false;
|
|
61
|
+
if (this.isConnected) this.#sync();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#authoredSlot(name) {
|
|
66
|
+
return this.querySelector(
|
|
67
|
+
`:scope > [slot="${name}"]:not([data-fig-property-generated])`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#generatedSlot(name) {
|
|
72
|
+
return this.querySelector(
|
|
73
|
+
`:scope > [slot="${name}"][data-fig-property-generated]`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#sync() {
|
|
78
|
+
this.#syncLabel();
|
|
79
|
+
this.#syncLeading();
|
|
80
|
+
this.#syncTrailing();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#syncLabel() {
|
|
84
|
+
const labelText = this.getAttribute("label");
|
|
85
|
+
const authoredLabel =
|
|
86
|
+
this.querySelector(
|
|
87
|
+
':scope > [slot="label"]:not([data-fig-property-generated])',
|
|
88
|
+
) ||
|
|
89
|
+
this.querySelector(
|
|
90
|
+
":scope > label:not([data-fig-property-generated])",
|
|
91
|
+
);
|
|
92
|
+
let generatedLabel = this.querySelector(
|
|
93
|
+
":scope > label[data-fig-property-generated]",
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (labelText === null || authoredLabel) {
|
|
97
|
+
generatedLabel?.remove();
|
|
98
|
+
generatedLabel = null;
|
|
99
|
+
} else {
|
|
100
|
+
if (!generatedLabel) {
|
|
101
|
+
generatedLabel = document.createElement("label");
|
|
102
|
+
generatedLabel.setAttribute("slot", "label");
|
|
103
|
+
generatedLabel.setAttribute("data-fig-property-generated", "");
|
|
104
|
+
this.insertBefore(generatedLabel, this.input);
|
|
105
|
+
}
|
|
106
|
+
generatedLabel.textContent = labelText;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const label = authoredLabel || generatedLabel;
|
|
110
|
+
this.#associateLabel(label, this.input);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#associateLabel(label, input) {
|
|
114
|
+
this.#clearLabelAssociation();
|
|
115
|
+
if (!(label instanceof HTMLElement) || !(input instanceof HTMLElement)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!label.id) {
|
|
120
|
+
figPropertyId += 1;
|
|
121
|
+
label.id = `fig-property-label-${figPropertyId}`;
|
|
122
|
+
label.setAttribute("data-fig-property-generated-id", "");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
!input.hasAttribute("aria-label") &&
|
|
127
|
+
!input.hasAttribute("aria-labelledby")
|
|
128
|
+
) {
|
|
129
|
+
input.setAttribute("aria-labelledby", label.id);
|
|
130
|
+
input.setAttribute("data-fig-property-labelledby", "");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
label.addEventListener("click", this.#focusInput);
|
|
134
|
+
this.#labelElement = label;
|
|
135
|
+
this.#associatedInput = input;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#clearLabelAssociation() {
|
|
139
|
+
this.#labelElement?.removeEventListener("click", this.#focusInput);
|
|
140
|
+
if (this.#associatedInput?.hasAttribute("data-fig-property-labelledby")) {
|
|
141
|
+
this.#associatedInput.removeAttribute("aria-labelledby");
|
|
142
|
+
this.#associatedInput.removeAttribute("data-fig-property-labelledby");
|
|
143
|
+
}
|
|
144
|
+
this.#labelElement = null;
|
|
145
|
+
this.#associatedInput = null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#focusInput = () => {
|
|
149
|
+
const input = this.input;
|
|
150
|
+
if (!(input instanceof HTMLElement)) return;
|
|
151
|
+
input.focus();
|
|
152
|
+
if (document.activeElement !== input) {
|
|
153
|
+
input.querySelector("input, select, textarea, button")?.focus();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
#syncLeading() {
|
|
158
|
+
const authored = this.#authoredSlot("leading");
|
|
159
|
+
const generated = this.#generatedSlot("leading");
|
|
160
|
+
const value = (this.getAttribute("leading") || "").trim().toLowerCase();
|
|
161
|
+
|
|
162
|
+
if (authored || !["drag", "chevron"].includes(value)) {
|
|
163
|
+
generated?.remove();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const iconName = value === "drag" ? "drag-handle" : "chevron";
|
|
168
|
+
if (generated?.getAttribute("name") === iconName) return;
|
|
169
|
+
|
|
170
|
+
generated?.remove();
|
|
171
|
+
const icon = createIcon(iconName);
|
|
172
|
+
icon.setAttribute("slot", "leading");
|
|
173
|
+
icon.setAttribute("data-fig-property-generated", "");
|
|
174
|
+
this.prepend(icon);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#syncTrailing() {
|
|
178
|
+
const authored = this.#authoredSlot("trailing");
|
|
179
|
+
const generated = this.#generatedSlot("trailing");
|
|
180
|
+
const value = (this.getAttribute("trailing") || "").trim().toLowerCase();
|
|
181
|
+
|
|
182
|
+
if (authored || !["remove", "add", "bind"].includes(value)) {
|
|
183
|
+
generated?.remove();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (generated?.getAttribute("data-fig-property-action") === value) return;
|
|
188
|
+
|
|
189
|
+
generated?.remove();
|
|
190
|
+
const button = document.createElement("fig-button");
|
|
191
|
+
button.setAttribute("slot", "trailing");
|
|
192
|
+
button.setAttribute("variant", "ghost");
|
|
193
|
+
button.setAttribute("size", "small");
|
|
194
|
+
button.setAttribute("data-fig-property-generated", "");
|
|
195
|
+
button.setAttribute("data-fig-property-action", value);
|
|
196
|
+
button.setAttribute("aria-label", `${value[0].toUpperCase()}${value.slice(1)}`);
|
|
197
|
+
|
|
198
|
+
if (value === "bind") {
|
|
199
|
+
button.textContent = "Bind";
|
|
200
|
+
} else {
|
|
201
|
+
button.setAttribute("icon", "");
|
|
202
|
+
button.append(createIcon(value === "add" ? "add" : "minus"));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
button.addEventListener("click", () => {
|
|
206
|
+
this.dispatchEvent(
|
|
207
|
+
new CustomEvent(value, {
|
|
208
|
+
bubbles: true,
|
|
209
|
+
composed: true,
|
|
210
|
+
detail: { input: this.input },
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
this.append(button);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
defineElement("fig-property", FigProperty);
|
|
219
|
+
|
|
220
|
+
export { FigProperty };
|
package/fig.js
CHANGED
|
@@ -23,6 +23,7 @@ function createFigIcon(name, options = {}) {
|
|
|
23
23
|
const icon = document.createElement("fig-icon");
|
|
24
24
|
if (name) icon.setAttribute("name", name);
|
|
25
25
|
if (options.size) icon.setAttribute("size", options.size);
|
|
26
|
+
if (options.slot) icon.setAttribute("slot", options.slot);
|
|
26
27
|
if (options.className) icon.className = options.className;
|
|
27
28
|
return icon;
|
|
28
29
|
}
|
|
@@ -8684,7 +8685,11 @@ class FigField extends HTMLElement {
|
|
|
8684
8685
|
this.input = Array.from(this.childNodes).find((node) =>
|
|
8685
8686
|
node.nodeType === Node.ELEMENT_NODE &&
|
|
8686
8687
|
node.nodeName.toLowerCase().startsWith("fig-") &&
|
|
8687
|
-
!(
|
|
8688
|
+
!(
|
|
8689
|
+
node instanceof Element &&
|
|
8690
|
+
(node.classList.contains("fig-field-chevron") ||
|
|
8691
|
+
["leading", "trailing"].includes(node.getAttribute("slot")))
|
|
8692
|
+
),
|
|
8688
8693
|
);
|
|
8689
8694
|
this.#observeInputAttributes();
|
|
8690
8695
|
|
|
@@ -8714,6 +8719,7 @@ class FigField extends HTMLElement {
|
|
|
8714
8719
|
if (!this.#chevron || !this.#chevron.isConnected) {
|
|
8715
8720
|
this.#chevron = createFigIcon("chevron", {
|
|
8716
8721
|
size: "small",
|
|
8722
|
+
slot: "leading",
|
|
8717
8723
|
className: "fig-field-chevron",
|
|
8718
8724
|
});
|
|
8719
8725
|
this.insertBefore(this.#chevron, this.label);
|
|
@@ -17138,7 +17144,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
17138
17144
|
label: p.name,
|
|
17139
17145
|
},
|
|
17140
17146
|
[
|
|
17141
|
-
figCreateElement("
|
|
17147
|
+
figCreateElement("fig-icon", { slot: "prepend" }, icon),
|
|
17142
17148
|
figCreateElement("span", {}, p.name),
|
|
17143
17149
|
],
|
|
17144
17150
|
),
|
|
@@ -20793,6 +20799,7 @@ const FIG_ICON_TOKENS = {
|
|
|
20793
20799
|
warning: { medium: "--icon-24-warning", small: "--icon-16-warning" },
|
|
20794
20800
|
copy: { medium: "--icon-24-copy", small: "--icon-16-copy" },
|
|
20795
20801
|
trash: { medium: "--icon-24-trash", small: "--icon-16-trash" },
|
|
20802
|
+
"drag-handle": "--icon-16-drag-handle",
|
|
20796
20803
|
};
|
|
20797
20804
|
|
|
20798
20805
|
function figIconCssVar(name, size = "medium") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rogieking/figui3",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.5.0",
|
|
4
4
|
"description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
|
|
5
5
|
"author": "Rogie King",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -11,20 +11,24 @@
|
|
|
11
11
|
".": "./dist/fig.js",
|
|
12
12
|
"./fig.js": "./dist/fig.js",
|
|
13
13
|
"./fig-layer.js": "./dist/fig-layer.js",
|
|
14
|
+
"./fig-property.js": "./dist/fig-property.js",
|
|
14
15
|
"./fig-editor.js": "./dist/fig-editor.js",
|
|
15
16
|
"./fig-lab.js": "./dist/fig-lab.js",
|
|
16
17
|
"./fig.css": "./dist/fig.css",
|
|
17
18
|
"./fig-layer.css": "./dist/fig-layer.css",
|
|
19
|
+
"./fig-property.css": "./dist/fig-property.css",
|
|
18
20
|
"./fig-editor.css": "./dist/fig-editor.css",
|
|
19
21
|
"./fig-lab.css": "./dist/fig-lab.css",
|
|
20
22
|
"./base.css": "./dist/base.css",
|
|
21
23
|
"./components.css": "./dist/components.css",
|
|
22
24
|
"./src/fig.js": "./fig.js",
|
|
23
25
|
"./src/fig-layer.js": "./fig-layer.js",
|
|
26
|
+
"./src/fig-property.js": "./fig-property.js",
|
|
24
27
|
"./src/fig-editor.js": "./fig-editor.js",
|
|
25
28
|
"./src/fig-lab.js": "./fig-lab.js",
|
|
26
29
|
"./src/fig.css": "./fig.css",
|
|
27
30
|
"./src/fig-layer.css": "./fig-layer.css",
|
|
31
|
+
"./src/fig-property.css": "./fig-property.css",
|
|
28
32
|
"./src/fig-editor.css": "./fig-editor.css",
|
|
29
33
|
"./src/fig-lab.css": "./fig-lab.css",
|
|
30
34
|
"./src/base.css": "./base.css",
|
|
@@ -33,10 +37,12 @@
|
|
|
33
37
|
"files": [
|
|
34
38
|
"fig.js",
|
|
35
39
|
"fig-layer.js",
|
|
40
|
+
"fig-property.js",
|
|
36
41
|
"fig-editor.js",
|
|
37
42
|
"fig-lab.js",
|
|
38
43
|
"fig.css",
|
|
39
44
|
"fig-layer.css",
|
|
45
|
+
"fig-property.css",
|
|
40
46
|
"fig-editor.css",
|
|
41
47
|
"fig-lab.css",
|
|
42
48
|
"base.css",
|
|
@@ -51,12 +57,13 @@
|
|
|
51
57
|
"sideEffects": [
|
|
52
58
|
"*.css",
|
|
53
59
|
"./fig.js",
|
|
60
|
+
"./fig-property.js",
|
|
54
61
|
"./fig-lab.js",
|
|
55
62
|
"./fig-editor.js"
|
|
56
63
|
],
|
|
57
64
|
"scripts": {
|
|
58
65
|
"dev": "bun --hot server.ts",
|
|
59
|
-
"build": "bun build fig.js --minify --outdir dist && bun build fig-layer.js --minify --outdir dist && bun build fig-editor.js --minify --outdir dist --external ./fig.js --external ./fig-lab.js && bun build fig-lab.js --minify --outdir dist --external ./fig.js --external ./fig-editor.js && npm run build:css",
|
|
66
|
+
"build": "bun build fig.js --minify --outdir dist && bun build fig-layer.js --minify --outdir dist && bun build fig-property.js --minify --outdir dist --external ./fig.js && bun build fig-editor.js --minify --outdir dist --external ./fig.js --external ./fig-property.js --external ./fig-lab.js && bun build fig-lab.js --minify --outdir dist --external ./fig.js --external ./fig-editor.js && npm run build:css",
|
|
60
67
|
"build:css": "node scripts/build-css.mjs",
|
|
61
68
|
"dev:playground": "node playground/dev.mjs",
|
|
62
69
|
"build:playground": "cd playground && npm run build",
|