@spfx-fast/sharepoint-ui 0.0.0-stage → 0.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/AGENTS.md +211 -0
- package/LICENSE +21 -0
- package/README.md +897 -2
- package/components.json +5811 -0
- package/dist/appearance.d.ts +6 -0
- package/dist/appearance.js +217 -0
- package/dist/elements/accordion.d.ts +9 -0
- package/dist/elements/accordion.js +338 -0
- package/dist/elements/alert.d.ts +2 -0
- package/dist/elements/alert.js +62 -0
- package/dist/elements/avatar-group-primitives.d.ts +3 -0
- package/dist/elements/avatar-group-primitives.js +189 -0
- package/dist/elements/avatar-group.d.ts +2 -0
- package/dist/elements/avatar-group.js +110 -0
- package/dist/elements/avatar.d.ts +4 -0
- package/dist/elements/avatar.js +251 -0
- package/dist/elements/badge.d.ts +8 -0
- package/dist/elements/badge.js +182 -0
- package/dist/elements/breadcrumb.d.ts +4 -0
- package/dist/elements/breadcrumb.js +233 -0
- package/dist/elements/button-group.d.ts +1 -0
- package/dist/elements/button-group.js +20 -0
- package/dist/elements/button-variants.d.ts +6 -0
- package/dist/elements/button-variants.js +339 -0
- package/dist/elements/button.d.ts +5 -0
- package/dist/elements/button.js +233 -0
- package/dist/elements/card-primitives.d.ts +3 -0
- package/dist/elements/card-primitives.js +83 -0
- package/dist/elements/card.d.ts +5 -0
- package/dist/elements/card.js +213 -0
- package/dist/elements/carousel.d.ts +92 -0
- package/dist/elements/carousel.js +1112 -0
- package/dist/elements/color-picker.d.ts +24 -0
- package/dist/elements/color-picker.js +1004 -0
- package/dist/elements/combobox.d.ts +1 -0
- package/dist/elements/combobox.js +1012 -0
- package/dist/elements/data-grid-model.d.ts +117 -0
- package/dist/elements/data-grid-model.js +294 -0
- package/dist/elements/data-grid.d.ts +29 -0
- package/dist/elements/data-grid.js +1567 -0
- package/dist/elements/date-picker.d.ts +1 -0
- package/dist/elements/date-picker.js +486 -0
- package/dist/elements/dialog.d.ts +23 -0
- package/dist/elements/dialog.js +409 -0
- package/dist/elements/divider.d.ts +2 -0
- package/dist/elements/divider.js +66 -0
- package/dist/elements/drawer.d.ts +3 -0
- package/dist/elements/drawer.js +308 -0
- package/dist/elements/field-composition.d.ts +26 -0
- package/dist/elements/field-composition.js +133 -0
- package/dist/elements/field.d.ts +1 -0
- package/dist/elements/field.js +67 -0
- package/dist/elements/file-upload.d.ts +6 -0
- package/dist/elements/file-upload.js +211 -0
- package/dist/elements/floating-position.d.ts +26 -0
- package/dist/elements/floating-position.js +39 -0
- package/dist/elements/image.d.ts +28 -0
- package/dist/elements/image.js +38 -0
- package/dist/elements/info.d.ts +9 -0
- package/dist/elements/info.js +102 -0
- package/dist/elements/input.d.ts +5 -0
- package/dist/elements/input.js +255 -0
- package/dist/elements/label.d.ts +15 -0
- package/dist/elements/label.js +56 -0
- package/dist/elements/link.d.ts +1 -0
- package/dist/elements/link.js +92 -0
- package/dist/elements/list.d.ts +19 -0
- package/dist/elements/list.js +297 -0
- package/dist/elements/menu.d.ts +33 -0
- package/dist/elements/menu.js +1103 -0
- package/dist/elements/message-bar.d.ts +10 -0
- package/dist/elements/message-bar.js +299 -0
- package/dist/elements/nav.d.ts +32 -0
- package/dist/elements/nav.js +791 -0
- package/dist/elements/overflow.d.ts +9 -0
- package/dist/elements/overflow.js +218 -0
- package/dist/elements/persona.d.ts +3 -0
- package/dist/elements/persona.js +125 -0
- package/dist/elements/popover.d.ts +77 -0
- package/dist/elements/popover.js +840 -0
- package/dist/elements/progress-bar.d.ts +4 -0
- package/dist/elements/progress-bar.js +82 -0
- package/dist/elements/radio-group.d.ts +1 -0
- package/dist/elements/radio-group.js +303 -0
- package/dist/elements/rating-display.d.ts +2 -0
- package/dist/elements/rating-display.js +125 -0
- package/dist/elements/rating.d.ts +2 -0
- package/dist/elements/rating.js +206 -0
- package/dist/elements/rich-text-editor.d.ts +18 -0
- package/dist/elements/rich-text-editor.js +508 -0
- package/dist/elements/search.d.ts +3 -0
- package/dist/elements/search.js +207 -0
- package/dist/elements/select.d.ts +6 -0
- package/dist/elements/select.js +291 -0
- package/dist/elements/shared.d.ts +19 -0
- package/dist/elements/shared.js +396 -0
- package/dist/elements/skeleton.d.ts +6 -0
- package/dist/elements/skeleton.js +91 -0
- package/dist/elements/slider.d.ts +1 -0
- package/dist/elements/slider.js +248 -0
- package/dist/elements/spin-button.d.ts +7 -0
- package/dist/elements/spin-button.js +499 -0
- package/dist/elements/spinner.d.ts +4 -0
- package/dist/elements/spinner.js +124 -0
- package/dist/elements/swatch-picker.d.ts +5 -0
- package/dist/elements/swatch-picker.js +363 -0
- package/dist/elements/table.d.ts +21 -0
- package/dist/elements/table.js +261 -0
- package/dist/elements/tabs.d.ts +1 -0
- package/dist/elements/tabs.js +276 -0
- package/dist/elements/tag-picker.d.ts +29 -0
- package/dist/elements/tag-picker.js +697 -0
- package/dist/elements/tags.d.ts +21 -0
- package/dist/elements/tags.js +421 -0
- package/dist/elements/teaching-popover.d.ts +32 -0
- package/dist/elements/teaching-popover.js +412 -0
- package/dist/elements/text.d.ts +9 -0
- package/dist/elements/text.js +75 -0
- package/dist/elements/textarea.d.ts +1 -0
- package/dist/elements/textarea.js +182 -0
- package/dist/elements/toast.d.ts +25 -0
- package/dist/elements/toast.js +337 -0
- package/dist/elements/toaster.d.ts +6 -0
- package/dist/elements/toaster.js +356 -0
- package/dist/elements/toggle.d.ts +2 -0
- package/dist/elements/toggle.js +246 -0
- package/dist/elements/toolbar.d.ts +12 -0
- package/dist/elements/toolbar.js +381 -0
- package/dist/elements/tooltip.d.ts +76 -0
- package/dist/elements/tooltip.js +510 -0
- package/dist/elements/tree-model.d.ts +19 -0
- package/dist/elements/tree-model.js +34 -0
- package/dist/elements/tree.d.ts +5 -0
- package/dist/elements/tree.js +273 -0
- package/dist/elements/view-card.d.ts +2 -0
- package/dist/elements/view-card.js +78 -0
- package/dist/elements/virtual-list.d.ts +41 -0
- package/dist/elements/virtual-list.js +369 -0
- package/dist/elements.d.ts +72 -0
- package/dist/elements.js +207 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +66 -0
- package/dist/react.d.ts +4546 -0
- package/dist/react.js +6837 -0
- package/dist/theme.d.ts +169 -0
- package/dist/theme.js +167 -0
- package/dist/virtualizer-hooks.d.ts +80 -0
- package/dist/virtualizer-hooks.js +253 -0
- package/package.json +87 -4
- package/scripts/generate-token-index.mjs +43 -0
- package/scripts/sync-component-catalog-signatures.mjs +55 -0
- package/scripts/validate-component-catalog.mjs +44 -0
- package/skills/sharepoint-ui/SKILL.md +12 -0
- package/skills/sharepoint-ui/references/choosing-components.md +20 -0
- package/skills/sharepoint-ui/references/sharepoint-patterns.md +17 -0
- package/tokens.json +3777 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type OverflowState = {
|
|
2
|
+
hasOverflow: boolean;
|
|
3
|
+
itemVisibility: Record<string, boolean>;
|
|
4
|
+
groupVisibility: Record<string, "visible" | "hidden" | "overflow">;
|
|
5
|
+
};
|
|
6
|
+
export type OverflowAxis = "horizontal" | "vertical";
|
|
7
|
+
export declare function defineOverflowItem(): void;
|
|
8
|
+
export declare function defineOverflowDivider(): void;
|
|
9
|
+
export declare function defineOverflow(): void;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { base } from "./shared.js";
|
|
2
|
+
export function defineOverflowItem() {
|
|
3
|
+
if (customElements.get("spui-overflow-item"))
|
|
4
|
+
return;
|
|
5
|
+
class SpuiOverflowItem extends HTMLElement {
|
|
6
|
+
static observedAttributes = ["priority", "pinned", "group-id", "id"];
|
|
7
|
+
connectedCallback() { if (!this.shadowRoot) {
|
|
8
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
9
|
+
const style = document.createElement("style");
|
|
10
|
+
style.textContent = `${base}:host{display:inline-flex;flex:none;min-width:0;max-width:100%}:host([hidden]){display:none!important}slot{display:contents}::slotted(*){flex:none}@media(forced-colors:active){:host{forced-color-adjust:auto}}`;
|
|
11
|
+
shadow.append(style, document.createElement("slot"));
|
|
12
|
+
} }
|
|
13
|
+
}
|
|
14
|
+
customElements.define("spui-overflow-item", SpuiOverflowItem);
|
|
15
|
+
}
|
|
16
|
+
export function defineOverflowDivider() {
|
|
17
|
+
if (customElements.get("spui-overflow-divider"))
|
|
18
|
+
return;
|
|
19
|
+
class SpuiOverflowDivider extends HTMLElement {
|
|
20
|
+
connectedCallback() { if (!this.shadowRoot) {
|
|
21
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
22
|
+
const style = document.createElement("style");
|
|
23
|
+
style.textContent = `${base}:host{display:block;align-self:stretch;flex:none;inline-size:1px;min-block-size:24px;background:var(--spui-ui-stroke,var(--colorNeutralStroke2,#e1dfdd))}:host([has-child]){inline-size:auto;min-block-size:0;background:transparent}:host([hidden]){display:none!important}slot{display:contents}`;
|
|
24
|
+
const slot = document.createElement("slot");
|
|
25
|
+
slot.addEventListener("slotchange", () => this.toggleAttribute("has-child", slot.assignedNodes({ flatten: true }).length > 0));
|
|
26
|
+
shadow.append(style, slot);
|
|
27
|
+
} }
|
|
28
|
+
}
|
|
29
|
+
customElements.define("spui-overflow-divider", SpuiOverflowDivider);
|
|
30
|
+
}
|
|
31
|
+
export function defineOverflow() {
|
|
32
|
+
if (customElements.get("spui-overflow"))
|
|
33
|
+
return;
|
|
34
|
+
defineOverflowItem();
|
|
35
|
+
defineOverflowDivider();
|
|
36
|
+
class SpuiOverflow extends HTMLElement {
|
|
37
|
+
static observedAttributes = ["aria-label", "minimum-visible", "overflow-direction", "overflow-axis", "has-hidden-items", "padding"];
|
|
38
|
+
itemSlot;
|
|
39
|
+
triggerSlot;
|
|
40
|
+
row;
|
|
41
|
+
resizeObserver;
|
|
42
|
+
mutationObserver;
|
|
43
|
+
events;
|
|
44
|
+
state;
|
|
45
|
+
frame = 0;
|
|
46
|
+
transaction = false;
|
|
47
|
+
refresh() { this.schedule(); }
|
|
48
|
+
forceRefresh() { if (this.frame)
|
|
49
|
+
cancelAnimationFrame(this.frame); this.frame = 0; this.recompute(); }
|
|
50
|
+
connectedCallback() {
|
|
51
|
+
if (!this.shadowRoot)
|
|
52
|
+
this.render();
|
|
53
|
+
this.events?.abort();
|
|
54
|
+
this.events = new AbortController();
|
|
55
|
+
this.itemSlot.addEventListener("slotchange", this.schedule, { signal: this.events.signal });
|
|
56
|
+
this.triggerSlot.addEventListener("slotchange", this.schedule, { signal: this.events.signal });
|
|
57
|
+
this.resizeObserver?.disconnect();
|
|
58
|
+
this.resizeObserver = new ResizeObserver(this.schedule);
|
|
59
|
+
this.resizeObserver.observe(this);
|
|
60
|
+
this.mutationObserver?.disconnect();
|
|
61
|
+
this.mutationObserver = new MutationObserver(this.schedule);
|
|
62
|
+
this.mutationObserver.observe(this, { childList: true, subtree: true, attributes: true, attributeFilter: ["id", "priority", "pinned", "group-id", "data-overflow-id", "data-overflow-item", "data-overflow-divider", "style", "class"] });
|
|
63
|
+
window.addEventListener("resize", this.schedule, { signal: this.events.signal, passive: true });
|
|
64
|
+
document.fonts?.addEventListener("loadingdone", this.schedule, { signal: this.events.signal });
|
|
65
|
+
this.syncLabel();
|
|
66
|
+
this.schedule();
|
|
67
|
+
}
|
|
68
|
+
disconnectedCallback() {
|
|
69
|
+
this.events?.abort();
|
|
70
|
+
this.events = undefined;
|
|
71
|
+
this.resizeObserver?.disconnect();
|
|
72
|
+
this.resizeObserver = undefined;
|
|
73
|
+
this.mutationObserver?.disconnect();
|
|
74
|
+
this.mutationObserver = undefined;
|
|
75
|
+
if (this.frame)
|
|
76
|
+
cancelAnimationFrame(this.frame);
|
|
77
|
+
this.frame = 0;
|
|
78
|
+
this.transaction = false;
|
|
79
|
+
}
|
|
80
|
+
attributeChangedCallback() { if (this.isConnected) {
|
|
81
|
+
this.syncLabel();
|
|
82
|
+
this.schedule();
|
|
83
|
+
} }
|
|
84
|
+
render() {
|
|
85
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
86
|
+
const style = document.createElement("style");
|
|
87
|
+
style.textContent = `${base}:host{display:block;min-width:0;max-width:100%}.row{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--spui-overflow-gap,var(--spui-toolbar-gap,6px));min-width:0;max-width:100%;overflow:hidden}.row:dir(rtl){direction:rtl}:host([overflow-axis=vertical]) .row{height:100%;flex-direction:column;align-items:stretch}:host([overflow-axis=vertical]) ::slotted([slot=overflow-trigger]){margin-inline-start:0;margin-block-start:auto;align-self:flex-end}::slotted([slot=overflow-trigger]){display:inline-flex;flex:none;margin-inline-start:auto}::slotted([slot=overflow-trigger][hidden]){display:none!important}::slotted(spui-overflow-divider[hidden]){display:none!important}@media(forced-colors:active){.row{forced-color-adjust:auto}}`;
|
|
88
|
+
this.row = document.createElement("div");
|
|
89
|
+
this.row.className = "row";
|
|
90
|
+
this.row.setAttribute("part", "control");
|
|
91
|
+
this.itemSlot = document.createElement("slot");
|
|
92
|
+
this.triggerSlot = document.createElement("slot");
|
|
93
|
+
this.triggerSlot.name = "overflow-trigger";
|
|
94
|
+
this.row.append(this.itemSlot, this.triggerSlot);
|
|
95
|
+
shadow.append(style, this.row);
|
|
96
|
+
this.syncLabel();
|
|
97
|
+
}
|
|
98
|
+
syncLabel() {
|
|
99
|
+
if (!this.hasAttribute("role"))
|
|
100
|
+
this.setAttribute("role", "group");
|
|
101
|
+
if (!this.hasAttribute("aria-label"))
|
|
102
|
+
this.setAttribute("aria-label", "Overflow actions");
|
|
103
|
+
}
|
|
104
|
+
schedule = () => {
|
|
105
|
+
if (!this.isConnected || this.frame)
|
|
106
|
+
return;
|
|
107
|
+
this.frame = requestAnimationFrame(() => { this.frame = 0; this.recompute(); });
|
|
108
|
+
};
|
|
109
|
+
recompute() {
|
|
110
|
+
if (this.transaction || !this.row)
|
|
111
|
+
return;
|
|
112
|
+
const assigned = this.itemSlot.assignedElements({ flatten: true }).filter((item) => item instanceof HTMLElement);
|
|
113
|
+
const items = assigned.flatMap(root => [root, ...root.querySelectorAll("[data-overflow-item], [data-overflow-divider]")]).filter(item => item.localName === "spui-overflow-item" || item.localName === "spui-overflow-divider" || item.hasAttribute("data-overflow-item") || item.hasAttribute("data-overflow-divider"));
|
|
114
|
+
const overflowItems = items.filter(item => item.localName === "spui-overflow-item" || item.hasAttribute("data-overflow-item"));
|
|
115
|
+
const dividers = items.filter(item => item.localName === "spui-overflow-divider" || item.hasAttribute("data-overflow-divider"));
|
|
116
|
+
const trigger = this.triggerSlot.assignedElements({ flatten: true }).find((item) => item instanceof HTMLElement);
|
|
117
|
+
const vertical = this.getAttribute("overflow-axis") === "vertical";
|
|
118
|
+
const available = vertical ? this.row.clientHeight : this.row.clientWidth;
|
|
119
|
+
if (!overflowItems.length || available <= 0)
|
|
120
|
+
return;
|
|
121
|
+
this.transaction = true;
|
|
122
|
+
try {
|
|
123
|
+
for (const item of [...overflowItems, ...dividers, ...(trigger ? [trigger] : [])])
|
|
124
|
+
item.hidden = false;
|
|
125
|
+
const sizes = new Map([...overflowItems, ...dividers].map(item => [item, vertical ? item.getBoundingClientRect().height : item.getBoundingClientRect().width]));
|
|
126
|
+
const triggerSize = trigger ? (vertical ? trigger.getBoundingClientRect().height : trigger.getBoundingClientRect().width) : 0;
|
|
127
|
+
const gap = parseFloat(getComputedStyle(this.row)[vertical ? "rowGap" : "columnGap"]) || 0;
|
|
128
|
+
const minVisible = Math.max(0, Math.min(overflowItems.length, Number(this.getAttribute("minimum-visible")) || 0));
|
|
129
|
+
const direction = this.getAttribute("overflow-direction") === "start" ? "start" : "end";
|
|
130
|
+
const candidates = overflowItems.filter(item => !item.hasAttribute("pinned")).sort((a, b) => {
|
|
131
|
+
const priority = (Number(a.getAttribute("priority")) || 0) - (Number(b.getAttribute("priority")) || 0);
|
|
132
|
+
return priority || (direction === "end" ? overflowItems.indexOf(b) - overflowItems.indexOf(a) : overflowItems.indexOf(a) - overflowItems.indexOf(b));
|
|
133
|
+
});
|
|
134
|
+
const hidden = new Set();
|
|
135
|
+
let visible = overflowItems.slice();
|
|
136
|
+
const visibleDividers = () => dividers.filter(divider => visible.some(item => item.getAttribute("group-id") === divider.getAttribute("group-id")));
|
|
137
|
+
const rowWidth = () => {
|
|
138
|
+
const shown = items.filter(item => overflowItems.includes(item) ? !hidden.has(item) : visibleDividers().includes(item));
|
|
139
|
+
const parent = shown[0]?.parentElement;
|
|
140
|
+
const itemGap = parent === this ? gap : parent ? (parseFloat(getComputedStyle(parent)[vertical ? "rowGap" : "columnGap"]) || 0) : gap;
|
|
141
|
+
return shown.reduce((sum, item) => sum + (sizes.get(item) ?? 0), 0) + itemGap * Math.max(0, shown.length - 1);
|
|
142
|
+
};
|
|
143
|
+
const reserveMenu = hidden.size > 0 || this.hasAttribute("has-hidden-items");
|
|
144
|
+
const needsSpace = () => rowWidth() + (reserveMenu ? gap + triggerSize + Math.max(0, Number(this.getAttribute("padding")) || 0) : 0) > available;
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
if (!needsSpace() || visible.length <= minVisible)
|
|
147
|
+
break;
|
|
148
|
+
hidden.add(candidate);
|
|
149
|
+
visible = visible.filter(item => item !== candidate);
|
|
150
|
+
}
|
|
151
|
+
for (const item of overflowItems) {
|
|
152
|
+
item.hidden = hidden.has(item);
|
|
153
|
+
if (hidden.has(item))
|
|
154
|
+
item.setAttribute("aria-hidden", "true");
|
|
155
|
+
else
|
|
156
|
+
item.removeAttribute("aria-hidden");
|
|
157
|
+
item.dataset.overflowed = String(hidden.has(item));
|
|
158
|
+
}
|
|
159
|
+
const visibleGroups = new Set(visible.map(item => item.getAttribute("group-id")).filter((id) => Boolean(id)));
|
|
160
|
+
const hiddenGroups = new Set([...hidden].map(item => item.getAttribute("group-id")).filter((id) => Boolean(id)));
|
|
161
|
+
for (const divider of dividers) {
|
|
162
|
+
const group = divider.getAttribute("group-id");
|
|
163
|
+
divider.hidden = !group || !visibleGroups.has(group);
|
|
164
|
+
}
|
|
165
|
+
if (trigger)
|
|
166
|
+
trigger.hidden = !reserveMenu;
|
|
167
|
+
const groupVisibility = {};
|
|
168
|
+
for (const group of new Set([...visibleGroups, ...hiddenGroups])) {
|
|
169
|
+
const groupItems = overflowItems.filter(item => item.getAttribute("group-id") === group);
|
|
170
|
+
const visibleCount = groupItems.filter(item => !hidden.has(item)).length;
|
|
171
|
+
groupVisibility[group] = visibleCount === 0 ? "hidden" : visibleCount === groupItems.length ? "visible" : "overflow";
|
|
172
|
+
}
|
|
173
|
+
const itemId = (item) => item.getAttribute("data-overflow-id") || item.id || "";
|
|
174
|
+
const itemVisibility = Object.fromEntries(overflowItems.map(item => [itemId(item), !hidden.has(item)]).filter(([id]) => Boolean(id)));
|
|
175
|
+
const state = {
|
|
176
|
+
visibleIds: visible.map(itemId).filter(Boolean),
|
|
177
|
+
overflowIds: [...hidden].map(itemId).filter(Boolean),
|
|
178
|
+
hasOverflow: hidden.size > 0 || this.hasAttribute("has-hidden-items"),
|
|
179
|
+
itemVisibility,
|
|
180
|
+
groupVisibility
|
|
181
|
+
};
|
|
182
|
+
const changed = !this.state || JSON.stringify(this.state) !== JSON.stringify(state);
|
|
183
|
+
this.state = state;
|
|
184
|
+
this.dataset.visibleCount = String(state.visibleIds.length);
|
|
185
|
+
this.dataset.overflowCount = String(state.overflowIds.length);
|
|
186
|
+
this.dataset.overflowIds = state.overflowIds.join(",");
|
|
187
|
+
if (changed) {
|
|
188
|
+
let active = this.ownerDocument.activeElement;
|
|
189
|
+
while (active?.shadowRoot?.activeElement)
|
|
190
|
+
active = active.shadowRoot.activeElement;
|
|
191
|
+
if (trigger && active instanceof HTMLElement && [...hidden].some(item => item === active || item.contains(active)))
|
|
192
|
+
this.focusTrigger(trigger);
|
|
193
|
+
this.dispatchEvent(new CustomEvent("spui-overflow-change", { bubbles: true, composed: true, detail: state }));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
this.transaction = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
focusTrigger(trigger) {
|
|
201
|
+
const visit = (node) => {
|
|
202
|
+
if (node instanceof HTMLSlotElement)
|
|
203
|
+
return node.assignedNodes({ flatten: true }).map(visit).find(Boolean);
|
|
204
|
+
if (node instanceof HTMLElement) {
|
|
205
|
+
if (node.matches("button:not(:disabled),a[href],[tabindex]:not([tabindex='-1'])"))
|
|
206
|
+
return node;
|
|
207
|
+
const root = node.shadowRoot;
|
|
208
|
+
const target = root ? [...root.childNodes].map(visit).find(Boolean) : undefined;
|
|
209
|
+
if (target)
|
|
210
|
+
return target;
|
|
211
|
+
}
|
|
212
|
+
return [...node.childNodes].map(visit).find(Boolean);
|
|
213
|
+
};
|
|
214
|
+
visit(trigger)?.focus();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
customElements.define("spui-overflow", SpuiOverflow);
|
|
218
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { base } from "./shared.js";
|
|
2
|
+
import { defineAvatar } from "./avatar.js";
|
|
3
|
+
const avatarSizes = { "extra-small": 20, small: 28, medium: 32, large: 36, "extra-large": 40, huge: 56 };
|
|
4
|
+
const presenceNames = {
|
|
5
|
+
available: "Available", busy: "Busy", "do-not-disturb": "Do not disturb", away: "Away", offline: "Offline", unknown: "Presence unknown"
|
|
6
|
+
};
|
|
7
|
+
export function definePersona() {
|
|
8
|
+
if (customElements.get("spui-persona"))
|
|
9
|
+
return;
|
|
10
|
+
defineAvatar();
|
|
11
|
+
class SpuiPersona extends HTMLElement {
|
|
12
|
+
static observedAttributes = ["name", "image", "initials", "color", "size", "text-position", "text-alignment", "presence-only", "secondary-text", "tertiary-text", "presence", "presence-label"];
|
|
13
|
+
connectedCallback() { if (!this.shadowRoot)
|
|
14
|
+
this.render(); this.sync(); }
|
|
15
|
+
attributeChangedCallback() { if (this.isConnected)
|
|
16
|
+
this.sync(); }
|
|
17
|
+
sync() {
|
|
18
|
+
const name = this.getAttribute("name") ?? "";
|
|
19
|
+
const avatar = this.shadowRoot?.querySelector("spui-avatar");
|
|
20
|
+
if (avatar) {
|
|
21
|
+
avatar.setAttribute("name", name);
|
|
22
|
+
avatar.setAttribute("size", String(avatarSizes[this.getAttribute("size")] ?? avatarSizes.medium));
|
|
23
|
+
avatar.setAttribute("color", this.getAttribute("color") || "neutral");
|
|
24
|
+
avatar.setAttribute("decorative", "");
|
|
25
|
+
for (const attribute of ["image", "initials"]) {
|
|
26
|
+
const value = this.getAttribute(attribute);
|
|
27
|
+
if (value)
|
|
28
|
+
avatar.setAttribute(attribute, value);
|
|
29
|
+
else
|
|
30
|
+
avatar.removeAttribute(attribute);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (avatar)
|
|
34
|
+
avatar.hidden = this.hasAttribute("presence-only");
|
|
35
|
+
const primary = this.shadowRoot?.querySelector(".primary");
|
|
36
|
+
if (primary)
|
|
37
|
+
primary.textContent = name;
|
|
38
|
+
const secondary = this.shadowRoot?.querySelector(".secondary");
|
|
39
|
+
if (secondary) {
|
|
40
|
+
secondary.textContent = this.getAttribute("secondary-text") ?? "";
|
|
41
|
+
secondary.hidden = !secondary.textContent;
|
|
42
|
+
}
|
|
43
|
+
const tertiary = this.shadowRoot?.querySelector(".tertiary");
|
|
44
|
+
if (tertiary) {
|
|
45
|
+
tertiary.textContent = this.getAttribute("tertiary-text") ?? "";
|
|
46
|
+
tertiary.hidden = !tertiary.textContent;
|
|
47
|
+
}
|
|
48
|
+
const quaternary = this.shadowRoot?.querySelector(".quaternary");
|
|
49
|
+
if (quaternary)
|
|
50
|
+
quaternary.hidden = !quaternary.textContent;
|
|
51
|
+
const presence = this.getAttribute("presence");
|
|
52
|
+
const marker = this.shadowRoot?.querySelector(".presence");
|
|
53
|
+
const validPresence = presence && Object.hasOwn(presenceNames, presence) ? presence : undefined;
|
|
54
|
+
if (marker) {
|
|
55
|
+
marker.hidden = !validPresence;
|
|
56
|
+
if (validPresence) {
|
|
57
|
+
marker.dataset.presence = validPresence;
|
|
58
|
+
marker.setAttribute("aria-label", this.getAttribute("presence-label") || presenceNames[validPresence]);
|
|
59
|
+
marker.title = this.getAttribute("presence-label") || presenceNames[validPresence];
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
marker.removeAttribute("aria-label");
|
|
63
|
+
marker.removeAttribute("title");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
render() {
|
|
68
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
69
|
+
const style = document.createElement("style");
|
|
70
|
+
style.textContent = `${base}:host{--spui-persona-avatar-size:32px;--spui-persona-gap:10px;display:inline-flex;max-width:100%;min-width:0;color:var(--spui-ui-text,var(--spui-semantic-body-text,var(--colorNeutralForeground1,#242424)));font:inherit;vertical-align:middle}.layout{display:flex;align-items:center;gap:var(--spui-persona-gap);min-width:0;max-width:100%}.avatar-wrap{position:relative;display:grid;place-items:center;flex:none;width:var(--spui-persona-avatar-size);height:var(--spui-persona-avatar-size)}.avatar-fallback{display:grid;width:100%;height:100%}.presence{position:absolute;inset-inline-end:-1px;bottom:-1px;box-sizing:border-box;width:12px;height:12px;border:2px solid var(--spui-base-surface,var(--spui-ui-surface,var(--colorNeutralBackground1,#fff)));border-radius:50%;background:var(--spui-persona-presence-color,#808080)}.presence[data-presence=available]{--spui-persona-presence-color:var(--spui-persona-presence-available,var(--spui-presence-available,var(--spui-semantic-success-text,var(--colorPaletteGreenForeground1,var(--spui-ui-success,#13a10e)))))}.presence[data-presence=busy],.presence[data-presence=do-not-disturb]{--spui-persona-presence-color:var(--spui-persona-presence-busy,var(--spui-presence-busy,var(--spui-semantic-error-text,var(--colorPaletteRedForeground1,var(--spui-ui-danger,#c50f1f)))))}.presence[data-presence=away]{--spui-persona-presence-color:var(--spui-persona-presence-away,var(--spui-presence-away,var(--spui-semantic-warning-text,var(--colorPaletteMarigoldForeground1,var(--spui-ui-warning,#835b00)))))}.presence[data-presence=offline],.presence[data-presence=unknown]{--spui-persona-presence-color:var(--spui-persona-presence-offline,var(--spui-presence-offline,var(--spui-semantic-body-subtext,var(--colorNeutralForeground3,var(--spui-ui-muted,#8a8886)))))}.presence[data-presence=do-not-disturb]{box-shadow:inset 0 0 0 2px var(--spui-base-surface,var(--spui-ui-surface,#fff))}.details{display:flex;flex-direction:column;justify-content:flex-start;min-width:0;max-width:100%;line-height:1.3}.primary,.secondary,.tertiary{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.primary{font-size:14px;font-weight:600;color:var(--spui-ui-text,var(--colorNeutralForeground1,#242424))}.secondary,.tertiary{font-size:12px;color:var(--spui-ui-muted,var(--colorNeutralForeground2,#616161))}.tertiary{font-size:11px}:host([size=extra-small]){--spui-persona-avatar-size:20px;--spui-persona-gap:6px}:host([size=small]){--spui-persona-avatar-size:28px;--spui-persona-gap:8px}:host([size=large]){--spui-persona-avatar-size:36px}:host([size=extra-large]){--spui-persona-avatar-size:40px;--spui-persona-gap:12px}:host([size=huge]){--spui-persona-avatar-size:56px;--spui-persona-gap:14px}:host([size=huge]) .primary{font-size:var(--spui-persona-huge-primary-size,20px)}:host([size=huge]) .secondary{font-size:var(--spui-persona-huge-secondary-size,16px)}:host([text-position=before]) .layout{flex-direction:row-reverse}:host([text-position=below]) .layout{flex-direction:column;align-items:center;text-align:center}:host([text-position=below]) .details{align-items:center}:host([text-position=below]){max-width:12rem}:host([text-alignment=center]) .details{justify-content:center}:host([presence-only]) .avatar-wrap{width:auto;height:auto}:host([presence-only]) .avatar-fallback, :host([presence-only]) slot[name=avatar]{display:none}:host([presence-only]) .presence{position:static;inset:auto;width:16px;height:16px;border-width:0}::slotted([slot=avatar]){display:block;max-width:100%;max-height:100%}::slotted([slot=details]){min-width:0}@media(forced-colors:active){.presence{border-color:Canvas;background:Highlight}.presence[data-presence=offline],.presence[data-presence=unknown]{background:GrayText}}`;
|
|
71
|
+
style.textContent += `::slotted([slot=primary-text]),::slotted([slot=secondary-text]),::slotted([slot=tertiary-text]),::slotted([slot=quaternary-text]){display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::slotted([slot=primary-text]){font-size:14px;font-weight:600;color:var(--spui-ui-text,var(--colorNeutralForeground1,#242424))}::slotted([slot=secondary-text]),::slotted([slot=quaternary-text]){font-size:12px;color:var(--spui-ui-muted,var(--colorNeutralForeground2,#616161))}::slotted([slot=tertiary-text]){font-size:11px;color:var(--spui-ui-muted,var(--colorNeutralForeground2,#616161))}:host([size=huge])::slotted([slot=primary-text]){font-size:var(--spui-persona-huge-primary-size,20px)}`;
|
|
72
|
+
const layout = document.createElement("div");
|
|
73
|
+
layout.className = "layout";
|
|
74
|
+
layout.part.add("root");
|
|
75
|
+
const avatarWrap = document.createElement("span");
|
|
76
|
+
avatarWrap.className = "avatar-wrap";
|
|
77
|
+
avatarWrap.part.add("avatar-wrap");
|
|
78
|
+
const avatarSlot = document.createElement("slot");
|
|
79
|
+
avatarSlot.name = "avatar";
|
|
80
|
+
const fallback = document.createElement("spui-avatar");
|
|
81
|
+
fallback.className = "avatar-fallback";
|
|
82
|
+
fallback.part.add("avatar");
|
|
83
|
+
avatarSlot.append(fallback);
|
|
84
|
+
const presence = document.createElement("span");
|
|
85
|
+
presence.className = "presence";
|
|
86
|
+
presence.part.add("presence");
|
|
87
|
+
presence.setAttribute("role", "img");
|
|
88
|
+
avatarWrap.append(avatarSlot, presence);
|
|
89
|
+
const details = document.createElement("span");
|
|
90
|
+
details.className = "details";
|
|
91
|
+
details.part.add("details");
|
|
92
|
+
const primary = document.createElement("span");
|
|
93
|
+
primary.className = "primary";
|
|
94
|
+
primary.part.add("primary");
|
|
95
|
+
const secondary = document.createElement("span");
|
|
96
|
+
secondary.className = "secondary";
|
|
97
|
+
secondary.part.add("secondary");
|
|
98
|
+
const tertiary = document.createElement("span");
|
|
99
|
+
tertiary.className = "tertiary";
|
|
100
|
+
tertiary.part.add("tertiary");
|
|
101
|
+
const primarySlot = document.createElement("slot");
|
|
102
|
+
primarySlot.name = "primary-text";
|
|
103
|
+
primarySlot.append(primary);
|
|
104
|
+
const secondarySlot = document.createElement("slot");
|
|
105
|
+
secondarySlot.name = "secondary-text";
|
|
106
|
+
secondarySlot.append(secondary);
|
|
107
|
+
const tertiarySlot = document.createElement("slot");
|
|
108
|
+
tertiarySlot.name = "tertiary-text";
|
|
109
|
+
tertiarySlot.append(tertiary);
|
|
110
|
+
const quaternary = document.createElement("span");
|
|
111
|
+
quaternary.className = "quaternary";
|
|
112
|
+
quaternary.part.add("quaternary");
|
|
113
|
+
quaternary.hidden = true;
|
|
114
|
+
const quaternarySlot = document.createElement("slot");
|
|
115
|
+
quaternarySlot.name = "quaternary-text";
|
|
116
|
+
quaternarySlot.append(quaternary);
|
|
117
|
+
const detailsSlot = document.createElement("slot");
|
|
118
|
+
detailsSlot.name = "details";
|
|
119
|
+
details.append(primarySlot, secondarySlot, tertiarySlot, quaternarySlot, detailsSlot);
|
|
120
|
+
layout.append(avatarWrap, details);
|
|
121
|
+
shadow.append(style, layout);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
customElements.define("spui-persona", SpuiPersona);
|
|
125
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type FloatingAlign, type FloatingPlacement } from "./floating-position.js";
|
|
2
|
+
export type PopoverPosition = FloatingPlacement | "above" | "below" | "before" | "after";
|
|
3
|
+
export type PopoverOpenChangeData = {
|
|
4
|
+
open: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type PopoverAlignment = FloatingAlign;
|
|
7
|
+
export type PopoverAutoSize = boolean | "always" | "width" | "width-always" | "height" | "height-always";
|
|
8
|
+
export type PopoverBoundaryRect = {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
export type PopoverBoundary = Element | DOMRectReadOnly | PopoverBoundaryRect | readonly Element[] | "clippingParents" | "scrollParent" | "window" | null;
|
|
15
|
+
export type PopoverPositioningTargetRect = PopoverBoundaryRect & Pick<DOMRectReadOnly, "top" | "left" | "bottom" | "right">;
|
|
16
|
+
export type PopoverOffsetParams = {
|
|
17
|
+
positionedRect: PopoverBoundaryRect;
|
|
18
|
+
targetRect: PopoverBoundaryRect;
|
|
19
|
+
position: "above" | "below" | "before" | "after";
|
|
20
|
+
alignment?: PopoverAlignment;
|
|
21
|
+
};
|
|
22
|
+
export type PopoverOffset = number | {
|
|
23
|
+
mainAxis: number;
|
|
24
|
+
crossAxis?: number;
|
|
25
|
+
} | ((params: PopoverOffsetParams) => number | {
|
|
26
|
+
mainAxis: number;
|
|
27
|
+
crossAxis?: number;
|
|
28
|
+
});
|
|
29
|
+
export type PopoverBoundaryPadding = number | Partial<Record<"top" | "end" | "bottom" | "start", number>>;
|
|
30
|
+
export type PopoverTarget = Element | {
|
|
31
|
+
getBoundingClientRect: () => PopoverPositioningTargetRect;
|
|
32
|
+
contextElement?: Element;
|
|
33
|
+
} | null;
|
|
34
|
+
export type PopoverPositioningPlacement = "top" | "top-start" | "top-end" | "right" | "right-start" | "right-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end";
|
|
35
|
+
export type PopoverPositioningEndData = {
|
|
36
|
+
placement: PopoverPositioningPlacement;
|
|
37
|
+
escaped: boolean;
|
|
38
|
+
referenceHidden: boolean;
|
|
39
|
+
};
|
|
40
|
+
export type PopoverPositioningImperativeRef = {
|
|
41
|
+
updatePosition: () => void;
|
|
42
|
+
setTarget: (target: PopoverTarget) => void;
|
|
43
|
+
};
|
|
44
|
+
export type PopoverPositioningRef = {
|
|
45
|
+
readonly current: PopoverPositioningImperativeRef | null;
|
|
46
|
+
} | ((value: PopoverPositioningImperativeRef | null) => void) | null;
|
|
47
|
+
export type PopoverPositioningOptions = {
|
|
48
|
+
position?: PopoverPosition;
|
|
49
|
+
align?: PopoverAlignment;
|
|
50
|
+
offset?: PopoverOffset;
|
|
51
|
+
fallbackPositions?: Array<PopoverPosition | `${"above" | "below" | "before" | "after"}-${"start" | "end" | "top" | "bottom"}`>;
|
|
52
|
+
autoSize?: PopoverAutoSize;
|
|
53
|
+
matchTargetSize?: boolean | "width";
|
|
54
|
+
pinned?: boolean;
|
|
55
|
+
flipBoundary?: PopoverBoundary;
|
|
56
|
+
overflowBoundary?: PopoverBoundary;
|
|
57
|
+
overflowBoundaryPadding?: PopoverBoundaryPadding;
|
|
58
|
+
positionFixed?: boolean;
|
|
59
|
+
strategy?: "absolute" | "fixed";
|
|
60
|
+
target?: PopoverTarget;
|
|
61
|
+
coverTarget?: boolean;
|
|
62
|
+
arrowPadding?: number;
|
|
63
|
+
positioningRef?: PopoverPositioningRef;
|
|
64
|
+
onPositioningEnd?: (event: CustomEvent<PopoverPositioningEndData>) => void;
|
|
65
|
+
disableUpdateOnResize?: boolean;
|
|
66
|
+
shiftToCoverTarget?: boolean;
|
|
67
|
+
useTransform?: boolean;
|
|
68
|
+
};
|
|
69
|
+
export type PopoverPositioning = PopoverPosition | `${"above" | "below" | "before" | "after"}-${"start" | "end" | "top" | "bottom"}` | PopoverPositioningOptions;
|
|
70
|
+
export declare function definePopoverTrigger(): void;
|
|
71
|
+
export declare function definePopoverSurface(): void;
|
|
72
|
+
export declare function definePopover(): void;
|
|
73
|
+
export declare function definePopoverVariant(tagName: string, defaults?: {
|
|
74
|
+
label?: string;
|
|
75
|
+
trapFocus?: boolean;
|
|
76
|
+
arrow?: boolean;
|
|
77
|
+
}): void;
|