@rogieking/figui3 6.28.1 → 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 +3 -0
- 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/fig-lab.css +81 -0
- package/fig-lab.js +291 -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/package.json
CHANGED