@rogieking/figui3 6.28.0 → 6.29.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 +35 -2
- package/components.css +15 -3
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +6 -6
- package/dist/fig.css +1 -1
- package/dist/fig.js +32 -32
- package/fig-lab.css +81 -0
- package/fig-lab.js +291 -0
- package/fig.js +13 -0
- package/package.json +1 -1
package/fig-lab.css
CHANGED
|
@@ -239,6 +239,7 @@ fig-content:has(> propskit-group[name]:last-child) {
|
|
|
239
239
|
:is(
|
|
240
240
|
propskit-switch,
|
|
241
241
|
propskit-color,
|
|
242
|
+
propskit-gradient,
|
|
242
243
|
propskit-text,
|
|
243
244
|
propskit-number,
|
|
244
245
|
propskit-slider
|
|
@@ -456,6 +457,86 @@ propskit-color {
|
|
|
456
457
|
}
|
|
457
458
|
}
|
|
458
459
|
|
|
460
|
+
propskit-gradient {
|
|
461
|
+
--propskit-gradient-height: 2rem;
|
|
462
|
+
--propskit-gradient-vertical-padding: var(--spacer-1);
|
|
463
|
+
--propskit-gradient-horizontal-padding: var(--spacer-3);
|
|
464
|
+
--figma-focus-outline-offset: 1px;
|
|
465
|
+
|
|
466
|
+
display: block;
|
|
467
|
+
width: 100%;
|
|
468
|
+
height: calc(
|
|
469
|
+
var(--propskit-gradient-height) + var(--propskit-gradient-vertical-padding) * 2
|
|
470
|
+
);
|
|
471
|
+
padding: var(--propskit-gradient-vertical-padding)
|
|
472
|
+
var(--propskit-gradient-horizontal-padding);
|
|
473
|
+
|
|
474
|
+
&[disabled]:not([disabled="false"]) {
|
|
475
|
+
pointer-events: none;
|
|
476
|
+
|
|
477
|
+
label {
|
|
478
|
+
color: var(--figma-color-text-disabled);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
fig-field:not([direction="vertical"]) {
|
|
483
|
+
position: relative;
|
|
484
|
+
display: flex;
|
|
485
|
+
align-items: center;
|
|
486
|
+
gap: var(--spacer-2);
|
|
487
|
+
height: 100%;
|
|
488
|
+
padding: 0 var(--spacer-1) 0 var(--spacer-2);
|
|
489
|
+
border-radius: var(--radius-medium);
|
|
490
|
+
background-color: var(--figma-color-bg-secondary);
|
|
491
|
+
|
|
492
|
+
&::after {
|
|
493
|
+
content: "";
|
|
494
|
+
position: absolute;
|
|
495
|
+
inset: 0;
|
|
496
|
+
border-radius: inherit;
|
|
497
|
+
pointer-events: none;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
&:hover::after {
|
|
501
|
+
box-shadow: inset 0 0 0 1px var(--figma-color-bordertranslucent);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
& > label {
|
|
505
|
+
position: relative;
|
|
506
|
+
z-index: 1;
|
|
507
|
+
display: flex;
|
|
508
|
+
align-items: center;
|
|
509
|
+
flex: 0 1 auto;
|
|
510
|
+
max-width: 40%;
|
|
511
|
+
min-width: 0;
|
|
512
|
+
margin: 0;
|
|
513
|
+
padding: 0;
|
|
514
|
+
overflow: hidden;
|
|
515
|
+
color: var(--figma-color-text-secondary);
|
|
516
|
+
text-overflow: ellipsis;
|
|
517
|
+
white-space: nowrap;
|
|
518
|
+
pointer-events: none;
|
|
519
|
+
|
|
520
|
+
fig-icon:first-child {
|
|
521
|
+
margin-left: calc(var(--spacer-1) * -1);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
fig-input-gradient {
|
|
526
|
+
position: relative;
|
|
527
|
+
flex: 0 0 50%;
|
|
528
|
+
width: 50%;
|
|
529
|
+
min-width: 0;
|
|
530
|
+
margin-left: auto;
|
|
531
|
+
border-radius: var(--radius-medium);
|
|
532
|
+
|
|
533
|
+
&:hover {
|
|
534
|
+
outline: none !important;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
459
540
|
propskit-select {
|
|
460
541
|
--propskit-select-height: 2rem;
|
|
461
542
|
--propskit-select-vertical-padding: var(--spacer-1);
|
package/fig-lab.js
CHANGED
|
@@ -130,6 +130,33 @@ function figLabPropskitValuesEqual(value, defaultValue) {
|
|
|
130
130
|
return String(value ?? "") === String(defaultValue ?? "");
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function figLabPropskitJsonValuesEqual(value, defaultValue) {
|
|
134
|
+
const normalize = (input) => {
|
|
135
|
+
if (Array.isArray(input)) return input.map(normalize);
|
|
136
|
+
if (input && typeof input === "object") {
|
|
137
|
+
return Object.keys(input)
|
|
138
|
+
.sort()
|
|
139
|
+
.reduce((result, key) => {
|
|
140
|
+
result[key] = normalize(input[key]);
|
|
141
|
+
return result;
|
|
142
|
+
}, {});
|
|
143
|
+
}
|
|
144
|
+
return input;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const parsedValue = typeof value === "string" ? JSON.parse(value) : value;
|
|
149
|
+
const parsedDefault =
|
|
150
|
+
typeof defaultValue === "string" ? JSON.parse(defaultValue) : defaultValue;
|
|
151
|
+
return (
|
|
152
|
+
JSON.stringify(normalize(parsedValue)) ===
|
|
153
|
+
JSON.stringify(normalize(parsedDefault))
|
|
154
|
+
);
|
|
155
|
+
} catch {
|
|
156
|
+
return figLabPropskitValuesEqual(value, defaultValue);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
133
160
|
function figLabPerceivedColorTheme(context, color) {
|
|
134
161
|
if (!context || !color) return null;
|
|
135
162
|
const probe = document.createElement("span");
|
|
@@ -714,6 +741,269 @@ class PropskitColor extends HTMLElement {
|
|
|
714
741
|
}
|
|
715
742
|
figLabDefineElement("propskit-color", PropskitColor);
|
|
716
743
|
|
|
744
|
+
/* Field + Gradient wrapper */
|
|
745
|
+
class PropskitGradient extends HTMLElement {
|
|
746
|
+
#field = null;
|
|
747
|
+
#label = null;
|
|
748
|
+
#input = null;
|
|
749
|
+
#hasCustomLabel = false;
|
|
750
|
+
#observer = null;
|
|
751
|
+
#managedInputAttrs = new Set();
|
|
752
|
+
#boundHandleInput = null;
|
|
753
|
+
#boundHandleChange = null;
|
|
754
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
755
|
+
#initialValue = null;
|
|
756
|
+
|
|
757
|
+
static get observedAttributes() {
|
|
758
|
+
return ["label", "direction", "aria-label"];
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
connectedCallback() {
|
|
762
|
+
if (!this.#field) this.#initialize();
|
|
763
|
+
this.#syncField();
|
|
764
|
+
this.#syncInputAttributes();
|
|
765
|
+
this.#bindInputEvents();
|
|
766
|
+
if (this.#initialValue === null) {
|
|
767
|
+
this.#initialValue =
|
|
768
|
+
this.getAttribute("default") ??
|
|
769
|
+
this.getAttribute("value") ??
|
|
770
|
+
JSON.stringify(this.#input?.value ?? {});
|
|
771
|
+
}
|
|
772
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
773
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
774
|
+
figLabConnectPropskitResetMenu(this);
|
|
775
|
+
|
|
776
|
+
if (!this.#observer) {
|
|
777
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
778
|
+
let syncField = false;
|
|
779
|
+
let syncInput = false;
|
|
780
|
+
|
|
781
|
+
for (const mutation of mutations) {
|
|
782
|
+
if (mutation.type !== "attributes") continue;
|
|
783
|
+
if (
|
|
784
|
+
mutation.attributeName === "label" ||
|
|
785
|
+
mutation.attributeName === "direction" ||
|
|
786
|
+
mutation.attributeName === "aria-label"
|
|
787
|
+
) {
|
|
788
|
+
syncField = true;
|
|
789
|
+
} else {
|
|
790
|
+
syncInput = true;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (syncField) this.#syncField();
|
|
795
|
+
if (syncInput) this.#syncInputAttributes();
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
this.#observer.observe(this, { attributes: true });
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
disconnectedCallback() {
|
|
803
|
+
this.#observer?.disconnect();
|
|
804
|
+
this.#unbindInputEvents();
|
|
805
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
806
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
810
|
+
if (oldValue === newValue || !this.#field) return;
|
|
811
|
+
if (name === "label" || name === "direction" || name === "aria-label") {
|
|
812
|
+
this.#syncField();
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
#initialize() {
|
|
817
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
818
|
+
(node) =>
|
|
819
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
820
|
+
);
|
|
821
|
+
const customLabel = initialChildren.find(
|
|
822
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
823
|
+
);
|
|
824
|
+
const field = document.createElement("fig-field");
|
|
825
|
+
const label = customLabel || document.createElement("label");
|
|
826
|
+
const input = document.createElement("fig-input-gradient");
|
|
827
|
+
|
|
828
|
+
field.append(label, input);
|
|
829
|
+
this.#field = field;
|
|
830
|
+
this.#label = label;
|
|
831
|
+
this.#input = input;
|
|
832
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
833
|
+
this.replaceChildren(field);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#syncField() {
|
|
837
|
+
if (!this.#field || !this.#label || !this.#input) return;
|
|
838
|
+
const hasLabelAttr = this.hasAttribute("label");
|
|
839
|
+
const rawLabel = this.getAttribute("label");
|
|
840
|
+
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
841
|
+
|
|
842
|
+
if (isBlankLabel) {
|
|
843
|
+
this.#label.remove();
|
|
844
|
+
} else {
|
|
845
|
+
if (!this.#hasCustomLabel) {
|
|
846
|
+
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
847
|
+
}
|
|
848
|
+
if (this.#label.parentElement !== this.#field) {
|
|
849
|
+
this.#field.prepend(this.#label);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
this.#field.setAttribute(
|
|
854
|
+
"direction",
|
|
855
|
+
this.getAttribute("direction") || "horizontal",
|
|
856
|
+
);
|
|
857
|
+
this.#input.setAttribute(
|
|
858
|
+
"aria-label",
|
|
859
|
+
this.getAttribute("aria-label") ||
|
|
860
|
+
this.#label.textContent?.trim() ||
|
|
861
|
+
"Gradient",
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
#getForwardedInputAttrNames() {
|
|
866
|
+
const reserved = new Set([
|
|
867
|
+
"label",
|
|
868
|
+
"direction",
|
|
869
|
+
"oninput",
|
|
870
|
+
"onchange",
|
|
871
|
+
"class",
|
|
872
|
+
"style",
|
|
873
|
+
"id",
|
|
874
|
+
"size",
|
|
875
|
+
"aria-label",
|
|
876
|
+
"default",
|
|
877
|
+
]);
|
|
878
|
+
return this.getAttributeNames().filter(
|
|
879
|
+
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
#syncInputAttributes() {
|
|
884
|
+
if (!this.#input) return;
|
|
885
|
+
const inputAttrs = this.#getForwardedInputAttrNames();
|
|
886
|
+
const nextManaged = new Set(inputAttrs);
|
|
887
|
+
|
|
888
|
+
for (const attrName of this.#managedInputAttrs) {
|
|
889
|
+
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
890
|
+
}
|
|
891
|
+
for (const attrName of inputAttrs) {
|
|
892
|
+
const next = this.getAttribute(attrName) ?? "";
|
|
893
|
+
if (
|
|
894
|
+
attrName === "value" &&
|
|
895
|
+
this.#input.getAttribute("value") === next
|
|
896
|
+
) {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
this.#input.setAttribute(attrName, next);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (!this.hasAttribute("edit")) this.#input.setAttribute("edit", "true");
|
|
903
|
+
const size = this.getAttribute("size");
|
|
904
|
+
if (!size || size === "large") this.#input.setAttribute("size", "large");
|
|
905
|
+
else this.#input.removeAttribute("size");
|
|
906
|
+
this.#managedInputAttrs = nextManaged;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
#bindInputEvents() {
|
|
910
|
+
if (!this.#input) return;
|
|
911
|
+
this.#boundHandleInput ??= this.#forwardInputEvent.bind(this, "input");
|
|
912
|
+
this.#boundHandleChange ??= this.#forwardInputEvent.bind(this, "change");
|
|
913
|
+
this.#input.addEventListener("input", this.#boundHandleInput);
|
|
914
|
+
this.#input.addEventListener("change", this.#boundHandleChange);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
#unbindInputEvents() {
|
|
918
|
+
if (!this.#input) return;
|
|
919
|
+
if (this.#boundHandleInput) {
|
|
920
|
+
this.#input.removeEventListener("input", this.#boundHandleInput);
|
|
921
|
+
}
|
|
922
|
+
if (this.#boundHandleChange) {
|
|
923
|
+
this.#input.removeEventListener("change", this.#boundHandleChange);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
#valueFromGradientEvent(event) {
|
|
928
|
+
const detail =
|
|
929
|
+
event instanceof CustomEvent && event.detail && typeof event.detail === "object"
|
|
930
|
+
? event.detail
|
|
931
|
+
: this.#input?.value;
|
|
932
|
+
if (!detail || typeof detail !== "object") return this.value;
|
|
933
|
+
return JSON.stringify(detail);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
#forwardInputEvent(type, event) {
|
|
937
|
+
event.stopImmediatePropagation();
|
|
938
|
+
const value = this.#valueFromGradientEvent(event);
|
|
939
|
+
this.setAttribute("value", value);
|
|
940
|
+
const detail =
|
|
941
|
+
event instanceof CustomEvent && event.detail !== undefined
|
|
942
|
+
? event.detail
|
|
943
|
+
: this.#input?.value;
|
|
944
|
+
this.dispatchEvent(
|
|
945
|
+
new CustomEvent(type, {
|
|
946
|
+
detail,
|
|
947
|
+
bubbles: true,
|
|
948
|
+
cancelable: true,
|
|
949
|
+
composed: true,
|
|
950
|
+
}),
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
#handleClick(event) {
|
|
955
|
+
if (
|
|
956
|
+
event.target instanceof Element &&
|
|
957
|
+
event.target.closest("fig-input-gradient, fig-menu")
|
|
958
|
+
) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
this.focus();
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
get value() {
|
|
965
|
+
return (
|
|
966
|
+
this.getAttribute("value") ??
|
|
967
|
+
JSON.stringify(this.#input?.value ?? {})
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
set value(nextValue) {
|
|
972
|
+
if (nextValue === null || nextValue === undefined || nextValue === "") {
|
|
973
|
+
this.removeAttribute("value");
|
|
974
|
+
this.#input?.removeAttribute("value");
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const next =
|
|
978
|
+
typeof nextValue === "object" ? JSON.stringify(nextValue) : String(nextValue);
|
|
979
|
+
this.setAttribute("value", next);
|
|
980
|
+
if (this.#input && this.#input.getAttribute("value") !== next) {
|
|
981
|
+
this.#input.setAttribute("value", next);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
get defaultValue() {
|
|
986
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "";
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
get isDefault() {
|
|
990
|
+
return figLabPropskitJsonValuesEqual(this.value, this.defaultValue);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
resetToDefault() {
|
|
994
|
+
const next = this.defaultValue;
|
|
995
|
+
if (!next) return;
|
|
996
|
+
this.value = next;
|
|
997
|
+
const detail = this.#input?.value;
|
|
998
|
+
figLabEmitPropskitReset(this, detail);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
focus(options) {
|
|
1002
|
+
this.#input?.focus(options);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
figLabDefineElement("propskit-gradient", PropskitGradient);
|
|
1006
|
+
|
|
717
1007
|
/* Field + Select wrapper */
|
|
718
1008
|
class PropskitSelect extends HTMLElement {
|
|
719
1009
|
#field = null;
|
|
@@ -1472,6 +1762,7 @@ class PropskitGroup extends HTMLElement {
|
|
|
1472
1762
|
|
|
1473
1763
|
static #CONTROL_SELECTOR = [
|
|
1474
1764
|
"propskit-color",
|
|
1765
|
+
"propskit-gradient",
|
|
1475
1766
|
"propskit-number",
|
|
1476
1767
|
"propskit-select",
|
|
1477
1768
|
"propskit-slider",
|
package/fig.js
CHANGED
|
@@ -10987,6 +10987,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
10987
10987
|
#arrowTooltipTimer = null;
|
|
10988
10988
|
#colorObserver = null;
|
|
10989
10989
|
#repositionRAF = null;
|
|
10990
|
+
#pickerDefinitionPromise = null;
|
|
10990
10991
|
#gradient = {
|
|
10991
10992
|
type: "linear",
|
|
10992
10993
|
angle: 90,
|
|
@@ -11060,11 +11061,22 @@ class FigInputGradient extends HTMLElement {
|
|
|
11060
11061
|
connectedCallback() {
|
|
11061
11062
|
this.#parseValue();
|
|
11062
11063
|
this.#render();
|
|
11064
|
+
this.#renderPickerWhenAvailable();
|
|
11063
11065
|
this.removeEventListener("keydown", this.#onPickerKeyDown);
|
|
11064
11066
|
this.addEventListener("keydown", this.#onPickerKeyDown);
|
|
11065
11067
|
if (this.#isEditable) document.addEventListener("keydown", this.#onKeyDown);
|
|
11066
11068
|
}
|
|
11067
11069
|
|
|
11070
|
+
#renderPickerWhenAvailable() {
|
|
11071
|
+
if (this.#editMode !== "picker" || hasFigFillPicker()) return;
|
|
11072
|
+
this.#pickerDefinitionPromise ??= customElements
|
|
11073
|
+
.whenDefined("fig-fill-picker")
|
|
11074
|
+
.then(() => {
|
|
11075
|
+
if (!this.isConnected || this.#editMode !== "picker") return;
|
|
11076
|
+
this.#render();
|
|
11077
|
+
});
|
|
11078
|
+
}
|
|
11079
|
+
|
|
11068
11080
|
disconnectedCallback() {
|
|
11069
11081
|
document.removeEventListener("keydown", this.#onKeyDown);
|
|
11070
11082
|
this.removeEventListener("keydown", this.#onPickerKeyDown);
|
|
@@ -11851,6 +11863,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
11851
11863
|
break;
|
|
11852
11864
|
case "edit":
|
|
11853
11865
|
this.#render();
|
|
11866
|
+
this.#renderPickerWhenAvailable();
|
|
11854
11867
|
if (this.#isEditable) {
|
|
11855
11868
|
document.addEventListener("keydown", this.#onKeyDown);
|
|
11856
11869
|
} else {
|
package/package.json
CHANGED