@rogieking/figui3 9.3.0 → 9.4.1
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-lab/SKILL.md +3 -3
- package/.cursor/skills/fig-lab/components.md +22 -2
- package/.cursor/skills/fig-lab/reference.md +22 -2
- package/.cursor/skills/figui3/SKILL.md +2 -2
- package/.cursor/skills/figui3/components.md +29 -0
- package/.cursor/skills/figui3/react.md +4 -1
- package/.cursor/skills/figui3/reference.md +10 -0
- package/CHANGELOG.md +3966 -0
- package/README.md +93 -0
- package/components.css +252 -15
- package/dist/components.css +1 -1
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +5 -5
- package/fig-lab.css +115 -0
- package/fig-lab.js +447 -5
- package/fig.js +613 -23
- package/package.json +2 -4
package/fig.js
CHANGED
|
@@ -1934,31 +1934,58 @@ figDefineElement("fig-tooltip", FigTooltip);
|
|
|
1934
1934
|
|
|
1935
1935
|
/* Text Truncation */
|
|
1936
1936
|
class FigTruncate extends HTMLElement {
|
|
1937
|
-
static observedAttributes = ["position", "tail"];
|
|
1937
|
+
static observedAttributes = ["position", "tail", "tooltip"];
|
|
1938
1938
|
|
|
1939
1939
|
#originalText = null;
|
|
1940
1940
|
#boundEnter = null;
|
|
1941
1941
|
#boundLeave = null;
|
|
1942
|
+
#observer = null;
|
|
1942
1943
|
|
|
1943
1944
|
connectedCallback() {
|
|
1944
1945
|
this.#originalText = this.textContent;
|
|
1946
|
+
this.#observer ??= new MutationObserver(() => {
|
|
1947
|
+
this.#originalText = this.textContent;
|
|
1948
|
+
this.#render();
|
|
1949
|
+
});
|
|
1950
|
+
this.#observe();
|
|
1945
1951
|
figNextFrame(this, () => {
|
|
1946
1952
|
this.#render();
|
|
1953
|
+
this.#teardownTooltip();
|
|
1947
1954
|
this.#setupTooltip();
|
|
1948
1955
|
});
|
|
1949
1956
|
}
|
|
1950
1957
|
|
|
1951
1958
|
disconnectedCallback() {
|
|
1959
|
+
this.#observer?.disconnect();
|
|
1952
1960
|
this.#teardownTooltip();
|
|
1953
1961
|
}
|
|
1954
1962
|
|
|
1955
1963
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
1956
1964
|
if (oldValue === newValue) return;
|
|
1957
1965
|
if (this.#originalText === null) return;
|
|
1966
|
+
if (name === "tooltip") {
|
|
1967
|
+
this.#teardownTooltip();
|
|
1968
|
+
if (this.isConnected) this.#setupTooltip();
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1958
1971
|
this.#render();
|
|
1959
1972
|
}
|
|
1960
1973
|
|
|
1974
|
+
#observe() {
|
|
1975
|
+
this.#observer?.observe(this, {
|
|
1976
|
+
childList: true,
|
|
1977
|
+
characterData: true,
|
|
1978
|
+
subtree: true,
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1961
1982
|
#render() {
|
|
1983
|
+
this.#observer?.disconnect();
|
|
1984
|
+
this.#renderContent();
|
|
1985
|
+
if (this.isConnected) this.#observe();
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
#renderContent() {
|
|
1962
1989
|
const position = this.getAttribute("position") || "right";
|
|
1963
1990
|
const text = this.#originalText || "";
|
|
1964
1991
|
if (position === "middle") {
|
|
@@ -1991,7 +2018,7 @@ class FigTruncate extends HTMLElement {
|
|
|
1991
2018
|
)
|
|
1992
2019
|
return;
|
|
1993
2020
|
this.#boundEnter = () => {
|
|
1994
|
-
if (this
|
|
2021
|
+
if (!this.#isTruncated()) return;
|
|
1995
2022
|
FigTooltip.show(this, this.#originalText);
|
|
1996
2023
|
};
|
|
1997
2024
|
this.#boundLeave = () => {
|
|
@@ -2001,11 +2028,21 @@ class FigTruncate extends HTMLElement {
|
|
|
2001
2028
|
this.addEventListener("pointerleave", this.#boundLeave);
|
|
2002
2029
|
}
|
|
2003
2030
|
|
|
2031
|
+
#isTruncated() {
|
|
2032
|
+
const overflows = (el) => el.scrollWidth > el.clientWidth;
|
|
2033
|
+
if (overflows(this)) return true;
|
|
2034
|
+
return [...this.querySelectorAll(":scope > .start, :scope > .end")].some(
|
|
2035
|
+
overflows,
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2004
2039
|
#teardownTooltip() {
|
|
2005
2040
|
if (this.#boundEnter)
|
|
2006
2041
|
this.removeEventListener("pointerenter", this.#boundEnter);
|
|
2007
2042
|
if (this.#boundLeave)
|
|
2008
2043
|
this.removeEventListener("pointerleave", this.#boundLeave);
|
|
2044
|
+
this.#boundEnter = null;
|
|
2045
|
+
this.#boundLeave = null;
|
|
2009
2046
|
FigTooltip.hide(this);
|
|
2010
2047
|
}
|
|
2011
2048
|
}
|
|
@@ -4798,6 +4835,513 @@ class FigTabs extends HTMLElement {
|
|
|
4798
4835
|
}
|
|
4799
4836
|
figDefineElement("fig-tabs", FigTabs);
|
|
4800
4837
|
|
|
4838
|
+
/* Toolbelt */
|
|
4839
|
+
/**
|
|
4840
|
+
* A semantic item group for use within FigToolbelt.
|
|
4841
|
+
*/
|
|
4842
|
+
class FigToolbeltGroup extends HTMLElement {
|
|
4843
|
+
connectedCallback() {
|
|
4844
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "group");
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
figDefineElement("fig-toolbelt-group", FigToolbeltGroup);
|
|
4848
|
+
|
|
4849
|
+
/**
|
|
4850
|
+
* A selectable item for use within FigToolbelt.
|
|
4851
|
+
* @attr {string} value - The value associated with the item
|
|
4852
|
+
* @attr {boolean} selected - Whether the item is currently selected
|
|
4853
|
+
* @attr {boolean} disabled - Whether the item is unavailable
|
|
4854
|
+
*/
|
|
4855
|
+
class FigToolbeltItem extends HTMLElement {
|
|
4856
|
+
static get observedAttributes() {
|
|
4857
|
+
return ["selected", "disabled"];
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4860
|
+
connectedCallback() {
|
|
4861
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "button");
|
|
4862
|
+
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "-1");
|
|
4863
|
+
this.#syncA11yState();
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
get value() {
|
|
4867
|
+
const value = this.getAttribute("value");
|
|
4868
|
+
return value !== null ? value : this.textContent?.trim() || "";
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
set value(value) {
|
|
4872
|
+
this.setAttribute("value", value ?? "");
|
|
4873
|
+
}
|
|
4874
|
+
|
|
4875
|
+
get selected() {
|
|
4876
|
+
return figBooleanAttribute(this, "selected");
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
set selected(value) {
|
|
4880
|
+
this.toggleAttribute("selected", Boolean(value));
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4883
|
+
get disabled() {
|
|
4884
|
+
return figBooleanAttribute(this, "disabled");
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
set disabled(value) {
|
|
4888
|
+
this.toggleAttribute("disabled", Boolean(value));
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
attributeChangedCallback() {
|
|
4892
|
+
this.#syncA11yState();
|
|
4893
|
+
}
|
|
4894
|
+
|
|
4895
|
+
#syncA11yState() {
|
|
4896
|
+
const selected = figBooleanAttribute(this, "selected");
|
|
4897
|
+
const disabled = figBooleanAttribute(this, "disabled");
|
|
4898
|
+
this.setAttribute("aria-pressed", selected ? "true" : "false");
|
|
4899
|
+
if (disabled) {
|
|
4900
|
+
this.setAttribute("aria-disabled", "true");
|
|
4901
|
+
this.setAttribute("tabindex", "-1");
|
|
4902
|
+
} else {
|
|
4903
|
+
this.removeAttribute("aria-disabled");
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
figDefineElement("fig-toolbelt-item", FigToolbeltItem);
|
|
4908
|
+
|
|
4909
|
+
/**
|
|
4910
|
+
* A selectable toolbar with configurable layout and overflow controls.
|
|
4911
|
+
* @attr {string} value - The selected item value
|
|
4912
|
+
* @attr {boolean} disabled - Disables all items
|
|
4913
|
+
* @attr {"horizontal"|"vertical"} layout - Item direction (default: horizontal)
|
|
4914
|
+
* @attr {"buttons"|"scrollbar"} overflow - Overflow controls (default: scrollbar)
|
|
4915
|
+
* @fires input - Selected value
|
|
4916
|
+
* @fires change - Selected value
|
|
4917
|
+
*/
|
|
4918
|
+
class FigToolbelt extends HTMLElement {
|
|
4919
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
4920
|
+
#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
4921
|
+
#boundSyncOverflow = this.#syncOverflow.bind(this);
|
|
4922
|
+
#viewport = null;
|
|
4923
|
+
#mutationObserver = null;
|
|
4924
|
+
#resizeObserver = null;
|
|
4925
|
+
#navStart = null;
|
|
4926
|
+
#navEnd = null;
|
|
4927
|
+
#parentDisabledItems = new WeakSet();
|
|
4928
|
+
#syncingValue = false;
|
|
4929
|
+
|
|
4930
|
+
constructor() {
|
|
4931
|
+
super();
|
|
4932
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
4933
|
+
const frame = document.createElement("div");
|
|
4934
|
+
frame.setAttribute("part", "frame");
|
|
4935
|
+
|
|
4936
|
+
const startSlot = document.createElement("slot");
|
|
4937
|
+
startSlot.name = "start";
|
|
4938
|
+
startSlot.style.display = "contents";
|
|
4939
|
+
|
|
4940
|
+
this.#viewport = document.createElement("div");
|
|
4941
|
+
this.#viewport.className = "fig-overflow-fade";
|
|
4942
|
+
this.#viewport.setAttribute("part", "viewport");
|
|
4943
|
+
const itemSlot = document.createElement("slot");
|
|
4944
|
+
itemSlot.style.display = "contents";
|
|
4945
|
+
this.#viewport.append(itemSlot);
|
|
4946
|
+
|
|
4947
|
+
const endSlot = document.createElement("slot");
|
|
4948
|
+
endSlot.name = "end";
|
|
4949
|
+
endSlot.style.display = "contents";
|
|
4950
|
+
|
|
4951
|
+
frame.append(startSlot, this.#viewport, endSlot);
|
|
4952
|
+
shadow.append(frame);
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4955
|
+
static get observedAttributes() {
|
|
4956
|
+
return ["value", "disabled", "layout", "overflow"];
|
|
4957
|
+
}
|
|
4958
|
+
|
|
4959
|
+
get #isVertical() {
|
|
4960
|
+
return this.getAttribute("layout") === "vertical";
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4963
|
+
get #overflowMode() {
|
|
4964
|
+
return this.getAttribute("overflow") === "buttons"
|
|
4965
|
+
? "buttons"
|
|
4966
|
+
: "scrollbar";
|
|
4967
|
+
}
|
|
4968
|
+
|
|
4969
|
+
connectedCallback() {
|
|
4970
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "toolbar");
|
|
4971
|
+
this.#syncOrientation();
|
|
4972
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
4973
|
+
this.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
4974
|
+
this.#viewport.addEventListener("scroll", this.#boundSyncOverflow);
|
|
4975
|
+
this.#applyOverflowMode();
|
|
4976
|
+
this.#startObserver();
|
|
4977
|
+
this.#startResizeObserver();
|
|
4978
|
+
figNextFrame(this, () => {
|
|
4979
|
+
this.#syncSelection();
|
|
4980
|
+
this.#applyDisabled(figBooleanAttribute(this, "disabled"));
|
|
4981
|
+
this.#syncTabIndexes();
|
|
4982
|
+
this.#syncOverflow();
|
|
4983
|
+
this.#scrollSelectedItemIntoView(undefined, "auto");
|
|
4984
|
+
});
|
|
4985
|
+
}
|
|
4986
|
+
|
|
4987
|
+
disconnectedCallback() {
|
|
4988
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
4989
|
+
this.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
4990
|
+
this.#viewport.removeEventListener("scroll", this.#boundSyncOverflow);
|
|
4991
|
+
this.#mutationObserver?.disconnect();
|
|
4992
|
+
this.#mutationObserver = null;
|
|
4993
|
+
this.#resizeObserver?.disconnect();
|
|
4994
|
+
this.#resizeObserver = null;
|
|
4995
|
+
this.#removeNavButtons();
|
|
4996
|
+
}
|
|
4997
|
+
|
|
4998
|
+
get value() {
|
|
4999
|
+
return this.selectedItem?.value || "";
|
|
5000
|
+
}
|
|
5001
|
+
|
|
5002
|
+
set value(value) {
|
|
5003
|
+
if (value === null || value === undefined) {
|
|
5004
|
+
this.removeAttribute("value");
|
|
5005
|
+
} else {
|
|
5006
|
+
this.setAttribute("value", String(value));
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
|
|
5010
|
+
get selectedItem() {
|
|
5011
|
+
return this.#items().find((item) => figBooleanAttribute(item, "selected")) || null;
|
|
5012
|
+
}
|
|
5013
|
+
|
|
5014
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
5015
|
+
if (oldValue === newValue) return;
|
|
5016
|
+
if (name === "value" && !this.#syncingValue) {
|
|
5017
|
+
this.#selectByValue(newValue);
|
|
5018
|
+
}
|
|
5019
|
+
if (name === "disabled") {
|
|
5020
|
+
this.#applyDisabled(newValue !== null && newValue !== "false");
|
|
5021
|
+
}
|
|
5022
|
+
if (name === "overflow") {
|
|
5023
|
+
this.#applyOverflowMode();
|
|
5024
|
+
requestAnimationFrame(() => this.#syncOverflow());
|
|
5025
|
+
}
|
|
5026
|
+
if (name === "layout") {
|
|
5027
|
+
this.#syncOrientation();
|
|
5028
|
+
if (this.#isVertical) this.#viewport.scrollLeft = 0;
|
|
5029
|
+
else this.#viewport.scrollTop = 0;
|
|
5030
|
+
this.#applyOverflowMode();
|
|
5031
|
+
requestAnimationFrame(() => {
|
|
5032
|
+
this.#syncOverflow();
|
|
5033
|
+
this.#scrollSelectedItemIntoView(undefined, "auto");
|
|
5034
|
+
});
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
|
|
5038
|
+
#items() {
|
|
5039
|
+
return Array.from(this.querySelectorAll("fig-toolbelt-item")).filter(
|
|
5040
|
+
(item) => item.closest("fig-toolbelt") === this,
|
|
5041
|
+
);
|
|
5042
|
+
}
|
|
5043
|
+
|
|
5044
|
+
#availableItems() {
|
|
5045
|
+
return this.#items().filter((item) => !figBooleanAttribute(item, "disabled"));
|
|
5046
|
+
}
|
|
5047
|
+
|
|
5048
|
+
#applyDisabled(disabled) {
|
|
5049
|
+
if (disabled) this.setAttribute("aria-disabled", "true");
|
|
5050
|
+
else this.removeAttribute("aria-disabled");
|
|
5051
|
+
for (const item of this.#items()) {
|
|
5052
|
+
if (disabled) {
|
|
5053
|
+
if (!figBooleanAttribute(item, "disabled")) {
|
|
5054
|
+
this.#parentDisabledItems.add(item);
|
|
5055
|
+
item.setAttribute("disabled", "");
|
|
5056
|
+
}
|
|
5057
|
+
} else if (this.#parentDisabledItems.has(item)) {
|
|
5058
|
+
this.#parentDisabledItems.delete(item);
|
|
5059
|
+
item.removeAttribute("disabled");
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
this.#syncTabIndexes();
|
|
5063
|
+
}
|
|
5064
|
+
|
|
5065
|
+
#syncSelection() {
|
|
5066
|
+
const declaredValue = this.getAttribute("value");
|
|
5067
|
+
if (declaredValue !== null) {
|
|
5068
|
+
this.#selectByValue(declaredValue);
|
|
5069
|
+
return;
|
|
5070
|
+
}
|
|
5071
|
+
|
|
5072
|
+
const selectedItems = this.#items().filter((item) =>
|
|
5073
|
+
figBooleanAttribute(item, "selected"),
|
|
5074
|
+
);
|
|
5075
|
+
const selected = selectedItems[0] || null;
|
|
5076
|
+
for (const item of selectedItems.slice(1)) {
|
|
5077
|
+
item.removeAttribute("selected");
|
|
5078
|
+
}
|
|
5079
|
+
if (selected) this.#reflectValue(selected.value);
|
|
5080
|
+
}
|
|
5081
|
+
|
|
5082
|
+
#selectByValue(value) {
|
|
5083
|
+
const target =
|
|
5084
|
+
value === null
|
|
5085
|
+
? null
|
|
5086
|
+
: this.#items().find((item) => item.value === value) || null;
|
|
5087
|
+
for (const item of this.#items()) {
|
|
5088
|
+
item.toggleAttribute("selected", item === target);
|
|
5089
|
+
}
|
|
5090
|
+
this.#syncTabIndexes(target);
|
|
5091
|
+
if (target) this.#scrollSelectedItemIntoView(target);
|
|
5092
|
+
}
|
|
5093
|
+
|
|
5094
|
+
#selectItem(target, { emit = false, focus = false } = {}) {
|
|
5095
|
+
if (!target || figBooleanAttribute(target, "disabled")) return false;
|
|
5096
|
+
const previousItem = this.selectedItem;
|
|
5097
|
+
const previousValue = this.value;
|
|
5098
|
+
for (const item of this.#items()) {
|
|
5099
|
+
item.toggleAttribute("selected", item === target);
|
|
5100
|
+
}
|
|
5101
|
+
this.#reflectValue(target.value);
|
|
5102
|
+
this.#syncTabIndexes(target);
|
|
5103
|
+
if (focus) target.focus();
|
|
5104
|
+
this.#scrollSelectedItemIntoView(target);
|
|
5105
|
+
if (emit && (previousItem !== target || previousValue !== target.value)) {
|
|
5106
|
+
this.#emitSelectionEvents();
|
|
5107
|
+
}
|
|
5108
|
+
return true;
|
|
5109
|
+
}
|
|
5110
|
+
|
|
5111
|
+
#reflectValue(value) {
|
|
5112
|
+
this.#syncingValue = true;
|
|
5113
|
+
this.setAttribute("value", value);
|
|
5114
|
+
this.#syncingValue = false;
|
|
5115
|
+
}
|
|
5116
|
+
|
|
5117
|
+
#syncTabIndexes(preferredItem) {
|
|
5118
|
+
const items = this.#items();
|
|
5119
|
+
const availableItems = this.#availableItems();
|
|
5120
|
+
const active =
|
|
5121
|
+
(preferredItem && availableItems.includes(preferredItem) && preferredItem) ||
|
|
5122
|
+
(availableItems.includes(document.activeElement) && document.activeElement) ||
|
|
5123
|
+
(availableItems.includes(this.selectedItem) && this.selectedItem) ||
|
|
5124
|
+
availableItems[0] ||
|
|
5125
|
+
null;
|
|
5126
|
+
for (const item of items) {
|
|
5127
|
+
item.setAttribute("tabindex", item === active ? "0" : "-1");
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
|
|
5131
|
+
#handleClick(event) {
|
|
5132
|
+
if (figBooleanAttribute(this, "disabled")) return;
|
|
5133
|
+
const target = event.target.closest?.("fig-toolbelt-item");
|
|
5134
|
+
if (!target || target.closest("fig-toolbelt") !== this) return;
|
|
5135
|
+
this.#selectItem(target, { emit: true });
|
|
5136
|
+
}
|
|
5137
|
+
|
|
5138
|
+
#handleKeyDown(event) {
|
|
5139
|
+
if (figBooleanAttribute(this, "disabled")) return;
|
|
5140
|
+
const target = event.target.closest?.("fig-toolbelt-item");
|
|
5141
|
+
if (!target || target.closest("fig-toolbelt") !== this) return;
|
|
5142
|
+
|
|
5143
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
5144
|
+
event.preventDefault();
|
|
5145
|
+
this.#selectItem(target, { emit: true });
|
|
5146
|
+
return;
|
|
5147
|
+
}
|
|
5148
|
+
|
|
5149
|
+
const items = this.#availableItems();
|
|
5150
|
+
if (!items.length) return;
|
|
5151
|
+
const currentIndex = Math.max(0, items.indexOf(target));
|
|
5152
|
+
let nextIndex = currentIndex;
|
|
5153
|
+
switch (event.key) {
|
|
5154
|
+
case "ArrowLeft":
|
|
5155
|
+
if (this.#isVertical) return;
|
|
5156
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
5157
|
+
break;
|
|
5158
|
+
case "ArrowUp":
|
|
5159
|
+
if (!this.#isVertical) return;
|
|
5160
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
5161
|
+
break;
|
|
5162
|
+
case "ArrowRight":
|
|
5163
|
+
if (this.#isVertical) return;
|
|
5164
|
+
nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
5165
|
+
break;
|
|
5166
|
+
case "ArrowDown":
|
|
5167
|
+
if (!this.#isVertical) return;
|
|
5168
|
+
nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
5169
|
+
break;
|
|
5170
|
+
case "Home":
|
|
5171
|
+
nextIndex = 0;
|
|
5172
|
+
break;
|
|
5173
|
+
case "End":
|
|
5174
|
+
nextIndex = items.length - 1;
|
|
5175
|
+
break;
|
|
5176
|
+
default:
|
|
5177
|
+
return;
|
|
5178
|
+
}
|
|
5179
|
+
event.preventDefault();
|
|
5180
|
+
this.#syncTabIndexes(items[nextIndex]);
|
|
5181
|
+
items[nextIndex].focus();
|
|
5182
|
+
this.#scrollItemIntoView(items[nextIndex]);
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
#emitSelectionEvents() {
|
|
5186
|
+
const detail = this.value;
|
|
5187
|
+
this.dispatchEvent(new CustomEvent("input", { detail, bubbles: true }));
|
|
5188
|
+
this.dispatchEvent(new CustomEvent("change", { detail, bubbles: true }));
|
|
5189
|
+
}
|
|
5190
|
+
|
|
5191
|
+
#startObserver() {
|
|
5192
|
+
this.#mutationObserver?.disconnect();
|
|
5193
|
+
this.#mutationObserver = new MutationObserver((records) => {
|
|
5194
|
+
const authoredChange = records.some(
|
|
5195
|
+
(record) =>
|
|
5196
|
+
record.type === "childList" ||
|
|
5197
|
+
(record.target instanceof Element &&
|
|
5198
|
+
record.target.matches("fig-toolbelt-item") &&
|
|
5199
|
+
record.target.closest("fig-toolbelt") === this),
|
|
5200
|
+
);
|
|
5201
|
+
if (!authoredChange) return;
|
|
5202
|
+
this.#applyOverflowMode();
|
|
5203
|
+
this.#syncSelection();
|
|
5204
|
+
this.#applyDisabled(figBooleanAttribute(this, "disabled"));
|
|
5205
|
+
this.#syncTabIndexes();
|
|
5206
|
+
requestAnimationFrame(() => {
|
|
5207
|
+
this.#syncOverflow();
|
|
5208
|
+
this.#scrollSelectedItemIntoView();
|
|
5209
|
+
});
|
|
5210
|
+
});
|
|
5211
|
+
this.#mutationObserver.observe(this, {
|
|
5212
|
+
childList: true,
|
|
5213
|
+
attributes: true,
|
|
5214
|
+
subtree: true,
|
|
5215
|
+
attributeFilter: ["value", "selected", "disabled"],
|
|
5216
|
+
});
|
|
5217
|
+
}
|
|
5218
|
+
|
|
5219
|
+
#startResizeObserver() {
|
|
5220
|
+
this.#resizeObserver?.disconnect();
|
|
5221
|
+
this.#resizeObserver = new ResizeObserver(() => this.#syncOverflow());
|
|
5222
|
+
this.#resizeObserver.observe(this);
|
|
5223
|
+
this.#resizeObserver.observe(this.#viewport);
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5226
|
+
#syncOverflow() {
|
|
5227
|
+
if (this.#overflowMode === "scrollbar") return;
|
|
5228
|
+
figSyncOverflowState(
|
|
5229
|
+
this,
|
|
5230
|
+
this.#viewport,
|
|
5231
|
+
this.#isVertical ? "y" : "x",
|
|
5232
|
+
);
|
|
5233
|
+
}
|
|
5234
|
+
|
|
5235
|
+
#syncOrientation() {
|
|
5236
|
+
this.setAttribute(
|
|
5237
|
+
"aria-orientation",
|
|
5238
|
+
this.#isVertical ? "vertical" : "horizontal",
|
|
5239
|
+
);
|
|
5240
|
+
this.#viewport.classList.add("fig-overflow-fade");
|
|
5241
|
+
this.#viewport.classList.toggle(
|
|
5242
|
+
"fig-overflow-fade-horizontal",
|
|
5243
|
+
!this.#isVertical,
|
|
5244
|
+
);
|
|
5245
|
+
}
|
|
5246
|
+
|
|
5247
|
+
#applyOverflowMode() {
|
|
5248
|
+
if (this.#overflowMode === "scrollbar") {
|
|
5249
|
+
this.#removeNavButtons();
|
|
5250
|
+
} else {
|
|
5251
|
+
this.#createNavButtons();
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
|
|
5255
|
+
#createNavButtons() {
|
|
5256
|
+
if (
|
|
5257
|
+
this.#navStart &&
|
|
5258
|
+
this.#navEnd &&
|
|
5259
|
+
this.contains(this.#navStart) &&
|
|
5260
|
+
this.contains(this.#navEnd)
|
|
5261
|
+
) {
|
|
5262
|
+
this.#positionNavButtons();
|
|
5263
|
+
return;
|
|
5264
|
+
}
|
|
5265
|
+
this.#navStart?.remove();
|
|
5266
|
+
this.#navEnd?.remove();
|
|
5267
|
+
const buttons = createFigOverflowButtons({
|
|
5268
|
+
owner: "toolbelt",
|
|
5269
|
+
onStart: () => this.#scrollByPage(-1),
|
|
5270
|
+
onEnd: () => this.#scrollByPage(1),
|
|
5271
|
+
});
|
|
5272
|
+
this.#navStart = buttons.start;
|
|
5273
|
+
this.#navEnd = buttons.end;
|
|
5274
|
+
this.#navStart.setAttribute("slot", "start");
|
|
5275
|
+
this.#navEnd.setAttribute("slot", "end");
|
|
5276
|
+
this.#positionNavButtons();
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
#positionNavButtons() {
|
|
5280
|
+
if (this.firstElementChild !== this.#navStart) this.prepend(this.#navStart);
|
|
5281
|
+
if (this.lastElementChild !== this.#navEnd) this.append(this.#navEnd);
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
#removeNavButtons() {
|
|
5285
|
+
this.#navStart?.remove();
|
|
5286
|
+
this.#navEnd?.remove();
|
|
5287
|
+
this.#navStart = null;
|
|
5288
|
+
this.#navEnd = null;
|
|
5289
|
+
this.classList.remove("overflow-start", "overflow-end");
|
|
5290
|
+
}
|
|
5291
|
+
|
|
5292
|
+
#scrollByPage(direction) {
|
|
5293
|
+
figScrollOverflowPage(
|
|
5294
|
+
this.#viewport,
|
|
5295
|
+
this.#isVertical ? "y" : "x",
|
|
5296
|
+
direction,
|
|
5297
|
+
);
|
|
5298
|
+
}
|
|
5299
|
+
|
|
5300
|
+
#scrollSelectedItemIntoView(
|
|
5301
|
+
item = this.selectedItem,
|
|
5302
|
+
behavior = "smooth",
|
|
5303
|
+
) {
|
|
5304
|
+
if (item) this.#scrollItemIntoView(item, behavior);
|
|
5305
|
+
}
|
|
5306
|
+
|
|
5307
|
+
#scrollItemIntoView(item, behavior = "smooth") {
|
|
5308
|
+
if (!item) return;
|
|
5309
|
+
requestAnimationFrame(() => {
|
|
5310
|
+
if (!this.isConnected || !item.isConnected) return;
|
|
5311
|
+
const isVertical = this.#isVertical;
|
|
5312
|
+
const hasOverflow = isVertical
|
|
5313
|
+
? this.#viewport.scrollHeight > this.#viewport.clientHeight
|
|
5314
|
+
: this.#viewport.scrollWidth > this.#viewport.clientWidth;
|
|
5315
|
+
if (!hasOverflow) return;
|
|
5316
|
+
const containerRect = this.#viewport.getBoundingClientRect();
|
|
5317
|
+
const itemRect = item.getBoundingClientRect();
|
|
5318
|
+
if (isVertical) {
|
|
5319
|
+
const itemCenter =
|
|
5320
|
+
itemRect.top -
|
|
5321
|
+
containerRect.top +
|
|
5322
|
+
this.#viewport.scrollTop +
|
|
5323
|
+
itemRect.height / 2;
|
|
5324
|
+
this.#viewport.scrollTo({
|
|
5325
|
+
top: itemCenter - this.#viewport.clientHeight / 2,
|
|
5326
|
+
behavior,
|
|
5327
|
+
});
|
|
5328
|
+
} else {
|
|
5329
|
+
const itemCenter =
|
|
5330
|
+
itemRect.left -
|
|
5331
|
+
containerRect.left +
|
|
5332
|
+
this.#viewport.scrollLeft +
|
|
5333
|
+
itemRect.width / 2;
|
|
5334
|
+
this.#viewport.scrollTo({
|
|
5335
|
+
left: itemCenter - this.#viewport.clientWidth / 2,
|
|
5336
|
+
behavior,
|
|
5337
|
+
});
|
|
5338
|
+
}
|
|
5339
|
+
this.#syncOverflow();
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
figDefineElement("fig-toolbelt", FigToolbelt);
|
|
5344
|
+
|
|
4801
5345
|
/* Segmented Control */
|
|
4802
5346
|
/**
|
|
4803
5347
|
* A custom segment element for use within FigSegmentedControl.
|
|
@@ -18191,23 +18735,29 @@ figDefineElement("fig-skeleton", FigSkeleton);
|
|
|
18191
18735
|
|
|
18192
18736
|
// FigGroup
|
|
18193
18737
|
class FigGroup extends HTMLElement {
|
|
18194
|
-
static observedAttributes = ["name", "collapsible", "open"];
|
|
18738
|
+
static observedAttributes = ["name", "collapsible", "open", "compact"];
|
|
18195
18739
|
|
|
18196
18740
|
#header = null;
|
|
18197
18741
|
#chevron = null;
|
|
18742
|
+
#toggle = null;
|
|
18743
|
+
#compactHeader = null;
|
|
18744
|
+
#compactHeaderValue = null;
|
|
18198
18745
|
|
|
18199
18746
|
connectedCallback() {
|
|
18200
18747
|
this.#render();
|
|
18201
18748
|
}
|
|
18202
18749
|
|
|
18203
18750
|
disconnectedCallback() {
|
|
18204
|
-
|
|
18205
|
-
|
|
18206
|
-
|
|
18751
|
+
this.#removeToggleListeners();
|
|
18752
|
+
}
|
|
18753
|
+
|
|
18754
|
+
#removeToggleListeners() {
|
|
18755
|
+
this.#chevron?.removeEventListener("click", this.#handleToggle);
|
|
18756
|
+
this.#toggle?.removeEventListener("click", this.#handleToggle);
|
|
18757
|
+
this.#toggle?.removeEventListener("keydown", this.#handleToggleKeyDown);
|
|
18207
18758
|
if (this.#header) {
|
|
18208
18759
|
this.#header.removeEventListener("click", this.#handleToggle);
|
|
18209
|
-
this.#header.removeEventListener("keydown", this.#
|
|
18210
|
-
this.#header.querySelector("h3")?.removeEventListener("click", this.#handleToggle);
|
|
18760
|
+
this.#header.removeEventListener("keydown", this.#handleToggleKeyDown);
|
|
18211
18761
|
}
|
|
18212
18762
|
}
|
|
18213
18763
|
|
|
@@ -18215,7 +18765,7 @@ class FigGroup extends HTMLElement {
|
|
|
18215
18765
|
if (oldValue === newValue) return;
|
|
18216
18766
|
if (!this.isConnected) return;
|
|
18217
18767
|
if (name === "open") {
|
|
18218
|
-
this.#
|
|
18768
|
+
this.#syncExpanded();
|
|
18219
18769
|
return;
|
|
18220
18770
|
}
|
|
18221
18771
|
this.#render();
|
|
@@ -18233,7 +18783,7 @@ class FigGroup extends HTMLElement {
|
|
|
18233
18783
|
} else {
|
|
18234
18784
|
this.setAttribute("open", "false");
|
|
18235
18785
|
}
|
|
18236
|
-
this.#
|
|
18786
|
+
this.#syncExpanded();
|
|
18237
18787
|
if (was !== !!value) {
|
|
18238
18788
|
this.dispatchEvent(
|
|
18239
18789
|
new CustomEvent("openchange", {
|
|
@@ -18244,12 +18794,46 @@ class FigGroup extends HTMLElement {
|
|
|
18244
18794
|
}
|
|
18245
18795
|
}
|
|
18246
18796
|
|
|
18797
|
+
#syncExpanded() {
|
|
18798
|
+
if (this.hasAttribute("collapsible")) {
|
|
18799
|
+
this.#toggle?.setAttribute("aria-expanded", String(this.open));
|
|
18800
|
+
} else {
|
|
18801
|
+
this.#toggle?.removeAttribute("aria-expanded");
|
|
18802
|
+
}
|
|
18803
|
+
}
|
|
18804
|
+
|
|
18805
|
+
#restoreHeaderCompact() {
|
|
18806
|
+
if (!this.#compactHeader) return;
|
|
18807
|
+
if (this.#compactHeaderValue === null) {
|
|
18808
|
+
this.#compactHeader.removeAttribute("compact");
|
|
18809
|
+
} else {
|
|
18810
|
+
this.#compactHeader.setAttribute("compact", this.#compactHeaderValue);
|
|
18811
|
+
}
|
|
18812
|
+
this.#compactHeader = null;
|
|
18813
|
+
this.#compactHeaderValue = null;
|
|
18814
|
+
}
|
|
18815
|
+
|
|
18816
|
+
#syncHeaderCompact() {
|
|
18817
|
+
const compact =
|
|
18818
|
+
this.hasAttribute("compact") && this.getAttribute("compact") !== "false";
|
|
18819
|
+
if (!compact) {
|
|
18820
|
+
this.#restoreHeaderCompact();
|
|
18821
|
+
return;
|
|
18822
|
+
}
|
|
18823
|
+
if (this.#compactHeader !== this.#header) {
|
|
18824
|
+
this.#restoreHeaderCompact();
|
|
18825
|
+
this.#compactHeader = this.#header;
|
|
18826
|
+
this.#compactHeaderValue = this.#header?.getAttribute("compact") ?? null;
|
|
18827
|
+
}
|
|
18828
|
+
this.#header?.setAttribute("compact", "");
|
|
18829
|
+
}
|
|
18830
|
+
|
|
18247
18831
|
#handleToggle = (e) => {
|
|
18248
18832
|
e.stopPropagation();
|
|
18249
18833
|
this.open = !this.open;
|
|
18250
18834
|
};
|
|
18251
18835
|
|
|
18252
|
-
#
|
|
18836
|
+
#handleToggleKeyDown = (e) => {
|
|
18253
18837
|
if (e.key !== "Enter" && e.key !== " ") return;
|
|
18254
18838
|
e.preventDefault();
|
|
18255
18839
|
e.stopPropagation();
|
|
@@ -18257,6 +18841,7 @@ class FigGroup extends HTMLElement {
|
|
|
18257
18841
|
};
|
|
18258
18842
|
|
|
18259
18843
|
#render() {
|
|
18844
|
+
this.#removeToggleListeners();
|
|
18260
18845
|
const isCollapsible = this.hasAttribute("collapsible");
|
|
18261
18846
|
const nameAttr = this.getAttribute("name");
|
|
18262
18847
|
const label = nameAttr || (isCollapsible ? "Group" : null);
|
|
@@ -18268,9 +18853,11 @@ class FigGroup extends HTMLElement {
|
|
|
18268
18853
|
|
|
18269
18854
|
if (!label && !isCollapsible && !userHeader) {
|
|
18270
18855
|
if (this.#header && this.#header.dataset.generated) {
|
|
18856
|
+
this.#restoreHeaderCompact();
|
|
18271
18857
|
this.#header.remove();
|
|
18272
18858
|
this.#header = null;
|
|
18273
18859
|
this.#chevron = null;
|
|
18860
|
+
this.#toggle = null;
|
|
18274
18861
|
}
|
|
18275
18862
|
return;
|
|
18276
18863
|
}
|
|
@@ -18286,6 +18873,7 @@ class FigGroup extends HTMLElement {
|
|
|
18286
18873
|
this.#header.dataset.generated = "true";
|
|
18287
18874
|
this.prepend(this.#header);
|
|
18288
18875
|
}
|
|
18876
|
+
this.#syncHeaderCompact();
|
|
18289
18877
|
|
|
18290
18878
|
// Ensure h3 exists inside header
|
|
18291
18879
|
let h3 = this.#header.querySelector("h3");
|
|
@@ -18293,6 +18881,7 @@ class FigGroup extends HTMLElement {
|
|
|
18293
18881
|
h3 = document.createElement("h3");
|
|
18294
18882
|
this.#header.prepend(h3);
|
|
18295
18883
|
}
|
|
18884
|
+
this.#toggle = h3;
|
|
18296
18885
|
if (!h3.id) h3.id = figUniqueId();
|
|
18297
18886
|
if (this.#header.dataset.generated) {
|
|
18298
18887
|
h3.textContent = label;
|
|
@@ -18312,26 +18901,27 @@ class FigGroup extends HTMLElement {
|
|
|
18312
18901
|
}
|
|
18313
18902
|
this.#header.insertBefore(chevron, h3);
|
|
18314
18903
|
this.#chevron = chevron;
|
|
18315
|
-
|
|
18316
|
-
this.#header.
|
|
18317
|
-
this.#header.
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
18321
|
-
|
|
18322
|
-
|
|
18904
|
+
this.#header.removeAttribute("role");
|
|
18905
|
+
this.#header.removeAttribute("tabindex");
|
|
18906
|
+
this.#header.removeAttribute("aria-expanded");
|
|
18907
|
+
h3.setAttribute("role", "button");
|
|
18908
|
+
h3.setAttribute("tabindex", "0");
|
|
18909
|
+
h3.setAttribute("aria-expanded", String(this.open));
|
|
18910
|
+
h3.addEventListener("click", this.#handleToggle);
|
|
18911
|
+
h3.addEventListener("keydown", this.#handleToggleKeyDown);
|
|
18912
|
+
chevron.addEventListener("click", this.#handleToggle);
|
|
18323
18913
|
|
|
18324
18914
|
if (!this.hasAttribute("open")) {
|
|
18325
18915
|
this.setAttribute("open", "false");
|
|
18326
|
-
|
|
18916
|
+
h3.setAttribute("aria-expanded", "false");
|
|
18327
18917
|
}
|
|
18328
18918
|
} else {
|
|
18329
|
-
h3.
|
|
18330
|
-
|
|
18919
|
+
h3.removeAttribute("role");
|
|
18920
|
+
h3.removeAttribute("tabindex");
|
|
18921
|
+
h3.removeAttribute("aria-expanded");
|
|
18331
18922
|
this.#header.removeAttribute("role");
|
|
18332
18923
|
this.#header.removeAttribute("tabindex");
|
|
18333
18924
|
this.#header.removeAttribute("aria-expanded");
|
|
18334
|
-
this.#header.removeEventListener("keydown", this.#handleHeaderKeyDown);
|
|
18335
18925
|
if (this.#chevron) {
|
|
18336
18926
|
this.#chevron.remove();
|
|
18337
18927
|
this.#chevron = null;
|