@spfx-fast/sharepoint-ui 0.0.0-stage → 0.4.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/AGENTS.md +211 -0
- package/LICENSE +21 -0
- package/README.md +897 -2
- package/components.json +5810 -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 +124 -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 +5 -0
- package/dist/elements/text.js +69 -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-theme-styles.d.ts +2 -0
- package/dist/react-theme-styles.js +75 -0
- package/dist/react.d.ts +4540 -0
- package/dist/react.js +6799 -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,261 @@
|
|
|
1
|
+
import { base } from "./shared.js";
|
|
2
|
+
export function createTableColumn(options) {
|
|
3
|
+
return { columnId: options.columnId, compare: options.compare ?? (() => 0), renderHeaderCell: options.renderHeaderCell ?? (() => null), renderCell: options.renderCell ?? (() => null) };
|
|
4
|
+
}
|
|
5
|
+
function defineSlotted({ tag, role, display, css, attributes = [], namedSlots = [], sync }) {
|
|
6
|
+
if (customElements.get(tag))
|
|
7
|
+
return;
|
|
8
|
+
class SpuiTablePart extends HTMLElement {
|
|
9
|
+
static observedAttributes = attributes;
|
|
10
|
+
connectedCallback() {
|
|
11
|
+
if (!this.shadowRoot) {
|
|
12
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
13
|
+
const style = document.createElement("style");
|
|
14
|
+
const closesHostRule = css.includes("}");
|
|
15
|
+
style.textContent = `${base}:host{box-sizing:border-box;display:${display};min-width:0;color:inherit;font:inherit;${css}${closesHostRule ? "" : "}"}`;
|
|
16
|
+
const slot = document.createElement("slot");
|
|
17
|
+
shadow.append(style, slot, ...namedSlots.map(name => { const named = document.createElement("slot"); named.name = name; return named; }));
|
|
18
|
+
if (!this.hasAttribute("role"))
|
|
19
|
+
this.setAttribute("role", role);
|
|
20
|
+
}
|
|
21
|
+
sync?.(this, this.shadowRoot);
|
|
22
|
+
}
|
|
23
|
+
attributeChangedCallback() { if (this.isConnected)
|
|
24
|
+
sync?.(this, this.shadowRoot); }
|
|
25
|
+
}
|
|
26
|
+
customElements.define(tag, SpuiTablePart);
|
|
27
|
+
}
|
|
28
|
+
export function defineTableCellActions() {
|
|
29
|
+
defineSlotted({ tag: "spui-table-cell-actions", role: "presentation", display: "inline-flex", attributes: ["visible"], css: `gap:var(--spui-table-action-gap,4px);align-items:center;margin-inline-start:auto;opacity:0;transition:opacity var(--spui-motion-duration-fast,120ms) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1))}:host([visible]),:host(:focus-within),:host(:hover){opacity:1}:host([hidden]){display:none}@media(prefers-reduced-motion:reduce){:host{transition:none}}@media(forced-colors:active){:host{opacity:1}}` });
|
|
30
|
+
}
|
|
31
|
+
export function defineTableCellLayout() {
|
|
32
|
+
defineSlotted({ tag: "spui-table-cell-layout", role: "presentation", display: "flex", attributes: ["appearance", "truncate"], namedSlots: ["media", "main", "description"], css: `align-items:center;gap:var(--spui-table-cell-gap,8px);min-width:0;min-height:inherit}:host([appearance=primary]){font-weight:600}:host([truncate]){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::slotted([slot=media]){display:inline-flex;flex:none;align-items:center}::slotted([slot=main]){flex:1;min-width:0}::slotted([slot=description]){color:var(--spui-ui-muted,var(--colorNeutralForeground2,#616161));font-size:.9em}` });
|
|
33
|
+
}
|
|
34
|
+
export function defineTableCell() {
|
|
35
|
+
defineSlotted({ tag: "spui-table-cell", role: "cell", display: "table-cell", css: `padding:var(--spui-table-cell-padding,10px 12px);border-block-end:1px solid var(--spui-ui-stroke,var(--colorNeutralStroke2,#e1dfdd));vertical-align:middle;overflow-wrap:anywhere}:host-context(spui-table[no-native-elements]){display:flex;flex:1 1 0;min-width:0;align-items:center}` });
|
|
36
|
+
}
|
|
37
|
+
export function defineTableHeaderCell() {
|
|
38
|
+
defineSlotted({
|
|
39
|
+
tag: "spui-table-header-cell", role: "columnheader", display: "table-cell", attributes: ["sortable", "sort-direction"], namedSlots: ["button", "aside"],
|
|
40
|
+
css: `position:relative;padding:var(--spui-table-header-padding,10px 12px);border-block-end:1px solid var(--spui-ui-stroke,var(--colorNeutralStroke1,#d1d1d1));color:var(--spui-ui-text,var(--colorNeutralForeground1,#242424));font-weight:600;text-align:start;vertical-align:middle}:host([sortable]){padding:0}:host([aria-sort=ascending]),:host([aria-sort=descending]){color:var(--spui-ui-accent,var(--colorBrandForeground1,#0f6cbd))}.sort-button,::slotted([slot=button]){box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:40px;padding:10px 12px;border:0;background:transparent;color:inherit;font:inherit;text-align:inherit}.sort-button[role=button],::slotted([slot=button][role=button]){cursor:pointer}.sort-button[role=button]:hover,::slotted([slot=button][role=button]:hover){background:var(--spui-ui-surface-hover,var(--colorNeutralBackground1Hover,#f5f5f5))}.sort-button[role=button]:focus-visible,::slotted([slot=button][role=button]:focus-visible){outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#0078d4));outline-offset:-2px}.sort-icon{display:inline-flex;align-items:center;justify-content:center;width:8px;height:8px;flex:none;opacity:.55}.sort-icon::before{content:"";display:block;width:6px;height:6px;border:solid currentColor;border-width:0 1.5px 1.5px 0;transform:rotate(45deg) translate(-1px,-1px)}.sort-icon.custom::before{display:none}.sort-icon slot::slotted(*){display:inline-flex;align-items:center;justify-content:center}:host([sort-direction=ascending]) .sort-icon::before{transform:rotate(225deg) translate(-1px,-1px)}:host(:not([sortable])) .sort-icon{display:none}@media(forced-colors:active){.sort-button:focus-visible,::slotted([slot=button][role=button]:focus-visible){outline-color:Highlight}}:host-context(spui-table[no-native-elements]){display:flex;flex:1 1 0;min-width:0;align-items:center}`,
|
|
41
|
+
sync: (host, root) => {
|
|
42
|
+
const sortable = host.hasAttribute("sortable");
|
|
43
|
+
host.toggleAttribute("data-sortable", sortable);
|
|
44
|
+
const direction = host.getAttribute("sort-direction");
|
|
45
|
+
if (direction === "ascending" || direction === "descending")
|
|
46
|
+
host.setAttribute("aria-sort", direction);
|
|
47
|
+
else
|
|
48
|
+
host.removeAttribute("aria-sort");
|
|
49
|
+
const buttonSlot = root.querySelector('slot[name="button"]');
|
|
50
|
+
let button = buttonSlot.querySelector(".sort-button");
|
|
51
|
+
if (!button) {
|
|
52
|
+
button = document.createElement("div");
|
|
53
|
+
button.className = "sort-button";
|
|
54
|
+
button.part.add("sort-button");
|
|
55
|
+
const content = document.createElement("span");
|
|
56
|
+
content.part.add("content");
|
|
57
|
+
const contentSlot = root.querySelector("slot:not([name])");
|
|
58
|
+
content.append(contentSlot);
|
|
59
|
+
const icon = document.createElement("span");
|
|
60
|
+
icon.className = "sort-icon";
|
|
61
|
+
icon.setAttribute("aria-hidden", "true");
|
|
62
|
+
icon.part.add("sort-icon");
|
|
63
|
+
const iconSlot = document.createElement("slot");
|
|
64
|
+
iconSlot.name = "sort-icon";
|
|
65
|
+
iconSlot.addEventListener("slotchange", () => icon.classList.toggle("custom", iconSlot.assignedNodes({ flatten: true }).length > 0));
|
|
66
|
+
icon.append(iconSlot);
|
|
67
|
+
button.append(content, icon);
|
|
68
|
+
buttonSlot.append(button);
|
|
69
|
+
}
|
|
70
|
+
if (sortable)
|
|
71
|
+
button.setAttribute("role", "button");
|
|
72
|
+
else
|
|
73
|
+
button.removeAttribute("role");
|
|
74
|
+
button.tabIndex = sortable ? 0 : -1;
|
|
75
|
+
const assignedButton = buttonSlot.assignedElements({ flatten: true }).find(element => element instanceof HTMLElement);
|
|
76
|
+
if (assignedButton) {
|
|
77
|
+
if (sortable) {
|
|
78
|
+
if (!assignedButton.hasAttribute("role"))
|
|
79
|
+
assignedButton.setAttribute("role", "button");
|
|
80
|
+
if (!assignedButton.hasAttribute("tabindex"))
|
|
81
|
+
assignedButton.tabIndex = 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!host.__spuiHeaderSortListener) {
|
|
85
|
+
host.__spuiHeaderSortListener = true;
|
|
86
|
+
const sort = () => {
|
|
87
|
+
if (!host.hasAttribute("sortable"))
|
|
88
|
+
return;
|
|
89
|
+
const next = host.getAttribute("sort-direction") === "ascending" ? "descending" : "ascending";
|
|
90
|
+
host.dispatchEvent(new CustomEvent("spui-table-sort", { bubbles: true, composed: true, detail: { direction: next, columnId: host.getAttribute("column-id") } }));
|
|
91
|
+
};
|
|
92
|
+
const isTrigger = (event) => event.composedPath().some(node => node === button || buttonSlot.assignedElements({ flatten: true }).includes(node));
|
|
93
|
+
host.addEventListener("click", event => { if (isTrigger(event))
|
|
94
|
+
sort(); });
|
|
95
|
+
host.addEventListener("keydown", event => {
|
|
96
|
+
if ((event.key === "Enter" || event.key === " ") && isTrigger(event)) {
|
|
97
|
+
const path = event.composedPath();
|
|
98
|
+
const target = buttonSlot.assignedElements({ flatten: true }).find(element => path.includes(element)) ?? button;
|
|
99
|
+
if (target.tagName === "BUTTON" || (event.key === "Enter" && target.tagName === "A"))
|
|
100
|
+
return;
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
sort();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export function defineTableRow() {
|
|
110
|
+
defineSlotted({ tag: "spui-table-row", role: "row", display: "table-row", attributes: ["appearance", "selected"], css: `color:var(--spui-ui-text,var(--colorNeutralForeground1,#242424))}:host(:hover){background:var(--spui-table-row-hover,var(--spui-ui-surface-hover,#f5f5f5))}:host([selected]){background:color-mix(in srgb,var(--spui-ui-accent,#0f6cbd) 10%,var(--spui-ui-surface,#fff))}:host([appearance=brand]){background:color-mix(in srgb,var(--spui-ui-accent,#0f6cbd) 10%,var(--spui-ui-surface,#fff))}:host([appearance=neutral]){background:var(--spui-ui-surface-hover,#f5f5f5)}:host([appearance=none]){background:transparent}:host([selected]) spui-table-cell,:host([selected]) spui-table-header-cell{font-weight:600}@media(forced-colors:active){:host([selected]){outline:1px solid Highlight}}:host-context(spui-table[no-native-elements]){display:flex;align-items:stretch;width:100%;min-width:100%}`, sync: host => { if (host.hasAttribute("selected"))
|
|
111
|
+
host.setAttribute("aria-selected", "true");
|
|
112
|
+
else
|
|
113
|
+
host.removeAttribute("aria-selected"); } });
|
|
114
|
+
}
|
|
115
|
+
export function defineTableHeader() {
|
|
116
|
+
defineSlotted({ tag: "spui-table-header", role: "rowgroup", display: "table-header-group", css: `background:var(--spui-table-header-background,var(--spui-ui-surface-subtle,var(--colorNeutralBackground2,#faf9f8)))}:host-context(spui-table[no-native-elements]){display:flex;flex-direction:column}` });
|
|
117
|
+
}
|
|
118
|
+
export function defineTableBody() {
|
|
119
|
+
defineSlotted({ tag: "spui-table-body", role: "rowgroup", display: "table-row-group", css: `:host-context(spui-table[no-native-elements]){display:flex;flex-direction:column}` });
|
|
120
|
+
}
|
|
121
|
+
export function defineTableSelectionCell() {
|
|
122
|
+
defineSlotted({
|
|
123
|
+
tag: "spui-table-selection-cell", role: "cell", display: "table-cell", attributes: ["type", "checked", "subtle", "invisible", "hidden"], namedSlots: ["checkbox-indicator", "radio-indicator"],
|
|
124
|
+
css: `position:relative;width:44px;min-width:44px;padding:4px 8px;text-align:center;vertical-align:middle}:host([hidden]){display:none}:host([invisible]){visibility:hidden}.select{box-sizing:border-box;display:inline-grid;place-items:center;width:20px;height:20px;margin:0;accent-color:var(--spui-ui-accent,#0f6cbd);cursor:pointer;transition:opacity var(--spui-motion-duration-fast,100ms) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1))}:host([data-custom-indicator]) .select{position:absolute;inset:0;margin:auto;opacity:0}:host([subtle]) .select{opacity:0}:host([subtle][checked]) .select,:host([subtle]:focus-within) .select,:host-context(spui-table-row:hover) .select{opacity:1}:host([data-custom-indicator]) .select{opacity:0}::slotted([slot=checkbox-indicator]),::slotted([slot=radio-indicator]){display:inline-flex;align-items:center;justify-content:center;pointer-events:none}::slotted([data-indeterminate=true]){forced-color-adjust:auto}.select:focus-visible{outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#0078d4));outline-offset:2px}:host([data-custom-indicator]:focus-within) ::slotted([data-checked]),:host([data-custom-indicator]:focus-within) ::slotted([slot=checkbox-indicator]),:host([data-custom-indicator]:focus-within) ::slotted([slot=radio-indicator]){outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#0078d4));outline-offset:2px;border-radius:var(--spui-control-radius,4px)}@media(prefers-reduced-motion:reduce){.select{transition:none}}@media(forced-colors:active){.select{accent-color:Highlight}:host([subtle]) .select{opacity:1}}:host-context(spui-table[no-native-elements]){display:flex;flex:0 0 44px;align-items:center;justify-content:center}`,
|
|
125
|
+
sync: (host, root) => {
|
|
126
|
+
let input = root.querySelector("input");
|
|
127
|
+
if (!input) {
|
|
128
|
+
input = document.createElement("input");
|
|
129
|
+
input.className = "select";
|
|
130
|
+
input.part.add("input");
|
|
131
|
+
root.append(input);
|
|
132
|
+
input.addEventListener("change", () => {
|
|
133
|
+
host.toggleAttribute("checked", input.checked);
|
|
134
|
+
host.dispatchEvent(new CustomEvent("spui-table-selection-change", { bubbles: true, composed: true, detail: { checked: input.checked, type: input.type } }));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
input.type = host.getAttribute("type") === "radio" ? "radio" : "checkbox";
|
|
138
|
+
input.checked = host.hasAttribute("checked");
|
|
139
|
+
input.indeterminate = host.getAttribute("checked") === "mixed";
|
|
140
|
+
const slotName = input.type === "radio" ? "radio-indicator" : "checkbox-indicator";
|
|
141
|
+
const indicatorSlot = root.querySelector(`slot[name="${slotName}"]`);
|
|
142
|
+
const indicators = indicatorSlot.assignedElements({ flatten: true });
|
|
143
|
+
host.toggleAttribute("data-custom-indicator", indicators.length > 0);
|
|
144
|
+
for (const indicator of indicators) {
|
|
145
|
+
indicator.setAttribute("data-checked", String(input.checked));
|
|
146
|
+
indicator.toggleAttribute("data-indeterminate", input.indeterminate);
|
|
147
|
+
}
|
|
148
|
+
input.setAttribute("aria-label", host.getAttribute("aria-label") || (input.type === "radio" ? "Select row" : "Select row"));
|
|
149
|
+
input.disabled = host.hasAttribute("disabled");
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
export function defineTableResizeHandle() {
|
|
154
|
+
defineSlotted({
|
|
155
|
+
tag: "spui-table-resize-handle", role: "presentation", display: "inline-flex", attributes: ["value", "min", "max", "step", "label"],
|
|
156
|
+
css: `position:absolute;inset-block:0;inset-inline-end:-4px;z-index:1;display:flex;align-items:center;justify-content:center;width:12px;min-height:40px;touch-action:none;cursor:col-resize}.handle{width:2px;height:60%;min-height:20px;border-radius:2px;background:var(--spui-ui-stroke,var(--colorNeutralStroke1,#d1d1d1));opacity:.55}.handle:focus-visible{outline:2px solid var(--spui-focus-ring,var(--spui-ui-accent,#0078d4));outline-offset:2px}:host(:hover) .handle,:host(:focus-visible) .handle{background:var(--spui-ui-accent,#0f6cbd);opacity:1}`,
|
|
157
|
+
sync: (host, root) => {
|
|
158
|
+
let handle = root.querySelector(".handle");
|
|
159
|
+
if (!handle) {
|
|
160
|
+
handle = document.createElement("div");
|
|
161
|
+
handle.className = "handle";
|
|
162
|
+
handle.tabIndex = 0;
|
|
163
|
+
handle.setAttribute("role", "separator");
|
|
164
|
+
handle.setAttribute("aria-orientation", "vertical");
|
|
165
|
+
handle.part.add("handle");
|
|
166
|
+
root.append(handle);
|
|
167
|
+
}
|
|
168
|
+
const value = Number(host.getAttribute("value"));
|
|
169
|
+
if (Number.isFinite(value))
|
|
170
|
+
handle.setAttribute("aria-valuenow", String(value));
|
|
171
|
+
else
|
|
172
|
+
handle.removeAttribute("aria-valuenow");
|
|
173
|
+
for (const name of ["min", "max"]) {
|
|
174
|
+
const item = host.getAttribute(name);
|
|
175
|
+
if (item)
|
|
176
|
+
handle.setAttribute(`aria-value${name}`, item);
|
|
177
|
+
else
|
|
178
|
+
handle.removeAttribute(`aria-value${name}`);
|
|
179
|
+
}
|
|
180
|
+
handle.setAttribute("aria-label", host.getAttribute("label") || "Resize column");
|
|
181
|
+
const resizeTo = (requested, sourceEvent) => {
|
|
182
|
+
const current = Number(host.getAttribute("value")) || 0;
|
|
183
|
+
const minAttribute = host.getAttribute("min");
|
|
184
|
+
const maxAttribute = host.getAttribute("max");
|
|
185
|
+
const minimum = minAttribute === null ? -Infinity : Number(minAttribute);
|
|
186
|
+
const maximum = maxAttribute === null ? Infinity : Number(maxAttribute);
|
|
187
|
+
const next = Math.max(Number.isFinite(minimum) ? minimum : -Infinity, Math.min(Number.isFinite(maximum) ? maximum : Infinity, requested));
|
|
188
|
+
if (next === current)
|
|
189
|
+
return;
|
|
190
|
+
host.setAttribute("value", String(next));
|
|
191
|
+
host.dispatchEvent(new CustomEvent("spui-table-resize", { bubbles: true, composed: true, detail: { delta: next - current, value: next, sourceEvent } }));
|
|
192
|
+
};
|
|
193
|
+
handle.onkeydown = event => {
|
|
194
|
+
if (!(event instanceof KeyboardEvent) || !["ArrowLeft", "ArrowRight"].includes(event.key))
|
|
195
|
+
return;
|
|
196
|
+
event.preventDefault();
|
|
197
|
+
const step = Math.max(1, Number(host.getAttribute("step")) || 8);
|
|
198
|
+
const rtl = getComputedStyle(host).direction === "rtl";
|
|
199
|
+
const delta = (event.key === "ArrowRight" ? 1 : -1) * (rtl ? -1 : 1) * step;
|
|
200
|
+
const current = Number(host.getAttribute("value")) || 0;
|
|
201
|
+
resizeTo(current + delta, event);
|
|
202
|
+
};
|
|
203
|
+
handle.onpointerdown = event => {
|
|
204
|
+
if (!(event instanceof PointerEvent) || event.button !== 0)
|
|
205
|
+
return;
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
const startX = event.clientX;
|
|
208
|
+
const startWidth = Number(host.getAttribute("value")) || 0;
|
|
209
|
+
const rtl = getComputedStyle(host).direction === "rtl";
|
|
210
|
+
const move = (moveEvent) => { if (moveEvent.pointerId === event.pointerId)
|
|
211
|
+
resizeTo(startWidth + (moveEvent.clientX - startX) * (rtl ? -1 : 1), moveEvent); };
|
|
212
|
+
const finish = (finishEvent) => {
|
|
213
|
+
if (finishEvent.pointerId !== event.pointerId)
|
|
214
|
+
return;
|
|
215
|
+
document.removeEventListener("pointermove", move);
|
|
216
|
+
document.removeEventListener("pointerup", finish);
|
|
217
|
+
document.removeEventListener("pointercancel", finish);
|
|
218
|
+
};
|
|
219
|
+
document.addEventListener("pointermove", move);
|
|
220
|
+
document.addEventListener("pointerup", finish);
|
|
221
|
+
document.addEventListener("pointercancel", finish);
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
export function defineTable() {
|
|
227
|
+
defineTableHeader();
|
|
228
|
+
defineTableBody();
|
|
229
|
+
defineTableRow();
|
|
230
|
+
defineTableHeaderCell();
|
|
231
|
+
defineTableCell();
|
|
232
|
+
defineTableCellLayout();
|
|
233
|
+
defineTableCellActions();
|
|
234
|
+
defineTableResizeHandle();
|
|
235
|
+
defineTableSelectionCell();
|
|
236
|
+
if (customElements.get("spui-table"))
|
|
237
|
+
return;
|
|
238
|
+
class SpuiTable extends HTMLElement {
|
|
239
|
+
static observedAttributes = ["size", "appearance", "no-native-elements"];
|
|
240
|
+
connectedCallback() {
|
|
241
|
+
if (this.shadowRoot)
|
|
242
|
+
return;
|
|
243
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
244
|
+
const style = document.createElement("style");
|
|
245
|
+
style.textContent = `${base}:host{box-sizing:border-box;display:block;width:var(--spui-table-width,100%);max-width:100%;min-width:0;background:var(--spui-table-background,var(--spui-ui-surface,#fff));color:var(--spui-ui-text,var(--colorNeutralForeground1,#242424));font-family:var(--spui-font-family,inherit);font-size:var(--spui-table-font-size,14px);line-height:1.4}.viewport{max-width:100%;overflow-x:auto}.table{display:table;width:100%;min-width:100%;border-collapse:collapse;border-spacing:0}:host([no-native-elements]) .table{display:flex;flex-direction:column}slot{display:contents}:host([size=small]){--spui-table-font-size:12px;--spui-table-cell-padding:6px 8px;--spui-table-header-padding:6px 8px}:host([size=large]){--spui-table-font-size:16px;--spui-table-cell-padding:14px 16px;--spui-table-header-padding:14px 16px}:host([appearance=dividers]){--spui-table-row-hover:transparent}:host([appearance=subtle]){--spui-table-header-background:transparent}@media(forced-colors:active){.table{border-color:CanvasText;color:CanvasText}}`;
|
|
246
|
+
const viewport = document.createElement("div");
|
|
247
|
+
viewport.className = "viewport";
|
|
248
|
+
viewport.part.add("viewport");
|
|
249
|
+
const table = document.createElement("div");
|
|
250
|
+
table.className = "table";
|
|
251
|
+
table.part.add("table");
|
|
252
|
+
table.setAttribute("role", "presentation");
|
|
253
|
+
table.append(document.createElement("slot"));
|
|
254
|
+
viewport.append(table);
|
|
255
|
+
shadow.append(style, viewport);
|
|
256
|
+
if (!this.hasAttribute("role"))
|
|
257
|
+
this.setAttribute("role", "table");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
customElements.define("spui-table", SpuiTable);
|
|
261
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function defineTabs(): void;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { base } from "./shared.js";
|
|
2
|
+
const valueOf = (element) => element.getAttribute("value") ?? "";
|
|
3
|
+
export function defineTabs() {
|
|
4
|
+
if (customElements.get("spui-tab"))
|
|
5
|
+
return;
|
|
6
|
+
class SpuiTab extends HTMLElement {
|
|
7
|
+
static observedAttributes = ["disabled", "data-list-disabled", "selected", "tabindex", "aria-controls", "appearance", "size", "vertical", "reserve-selected-tab-space"];
|
|
8
|
+
connectedCallback() { if (!this.shadowRoot)
|
|
9
|
+
this.render(); this.sync(); }
|
|
10
|
+
attributeChangedCallback() { if (this.isConnected)
|
|
11
|
+
this.sync(); }
|
|
12
|
+
get value() { return valueOf(this); }
|
|
13
|
+
get disabled() { return this.hasAttribute("disabled") || this.hasAttribute("data-list-disabled"); }
|
|
14
|
+
getContentText() {
|
|
15
|
+
const named = this.shadowRoot?.querySelector('slot[name="content"]')?.assignedNodes({ flatten: true });
|
|
16
|
+
const fallback = this.shadowRoot?.querySelector("slot:not([name])")?.assignedNodes({ flatten: true });
|
|
17
|
+
const assigned = named?.length ? named : fallback;
|
|
18
|
+
return (assigned?.length ? assigned : [...this.childNodes].filter(node => !(node instanceof Element && node.slot)))
|
|
19
|
+
.map(node => node.textContent ?? "").join("").replace(/\s+/g, " ").trim();
|
|
20
|
+
}
|
|
21
|
+
sync() {
|
|
22
|
+
this.setAttribute("role", "tab");
|
|
23
|
+
this.setAttribute("aria-selected", String(this.getAttribute("selected") === "true"));
|
|
24
|
+
if (this.disabled)
|
|
25
|
+
this.setAttribute("aria-disabled", "true");
|
|
26
|
+
else
|
|
27
|
+
this.removeAttribute("aria-disabled");
|
|
28
|
+
const reserved = this.shadowRoot?.querySelector(".reserved");
|
|
29
|
+
if (reserved)
|
|
30
|
+
reserved.textContent = this.hasAttribute("reserve-selected-tab-space") ? this.getContentText() : "";
|
|
31
|
+
}
|
|
32
|
+
render() {
|
|
33
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
34
|
+
const style = document.createElement("style");
|
|
35
|
+
style.textContent = `${base}:host{box-sizing:border-box;position:relative;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-width:44px;min-height:40px;padding:8px 12px;border:0;border-radius:var(--spui-control-radius,4px);background:transparent;color:var(--spui-ui-muted,var(--spui-semantic-body-text,#616161));font:inherit;line-height:1.25;text-align:center;white-space:nowrap;cursor:pointer;user-select:none}slot[name=icon]{display:inline-flex;align-items:center;justify-content:center;flex:none}.content{display:grid;min-width:0}.content slot,.reserved{grid-area:1/1}.reserved{display:none;visibility:hidden;white-space:pre;font-weight:600}:host([reserve-selected-tab-space][aria-selected=false]) .reserved{display:block}:host([size=small]){min-height:32px;padding:6px 10px;font-size:12px}:host([size=large]){min-height:48px;padding:10px 16px;font-size:16px}:host([aria-selected=true]){color:var(--spui-ui-text,var(--spui-semantic-body-text,#242424));font-weight:600}:host::after{position:absolute;inset-inline:10px;inset-block-end:0;height:2px;border-radius:2px;background:var(--spui-ui-accent,var(--spui-semantic-accent,#78716c));content:"";opacity:0;transform:scaleX(.65);transition:opacity var(--spui-motion-duration-fast,120ms) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1)),transform var(--spui-motion-duration-fast,120ms) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1))}:host([aria-selected=true])::after{opacity:1;transform:scaleX(1)}:host(:focus-visible){z-index:1;outline:2px solid var(--spui-focus-ring,var(--spui-semantic-focus-border,#78716c));outline-offset:-2px}:host([disabled]),:host([data-list-disabled]){color:var(--spui-ui-muted,#777);cursor:not-allowed;opacity:.6}:host([appearance=subtle-circular]),:host([appearance=filled-circular]){border-radius:999px}:host([appearance=subtle-circular]){border:1px solid var(--spui-ui-stroke,var(--spui-semantic-body-divider,#d1d1d1))}:host([appearance=subtle-circular][aria-selected=true]){background:color-mix(in srgb,var(--spui-ui-accent,#78716c) 12%,var(--spui-base-surface,var(--spui-ui-surface,#fff)))}:host([appearance=filled-circular][aria-selected=true]){background:color-mix(in srgb,var(--spui-ui-accent,#78716c) 18%,var(--spui-base-surface,var(--spui-ui-surface,#fff)))}:host([vertical]){justify-content:flex-start;width:100%;text-align:start}:host([vertical])::after{inset-block:8px;inset-inline-start:0;inset-inline-end:auto;width:2px;height:auto}:host([appearance=filled-circular])::after{display:none}@media(prefers-reduced-motion:reduce){:host::after{transition:none}}@media(forced-colors:active){:host::after{background:Highlight}:host(:focus-visible){outline-color:Highlight}:host([aria-selected=true]){color:CanvasText}}`;
|
|
36
|
+
style.textContent += `:host([appearance=transparent]:hover:not([disabled])),:host([appearance=transparent]:active:not([disabled])){background:var(--colorTransparentBackgroundHover,var(--spui-ui-surface-hover,#f5f5f5))}:host([appearance=transparent]:active:not([disabled])){background:var(--colorTransparentBackgroundPressed,var(--spui-ui-surface-hover,#e0e0e0))}:host([appearance=subtle]){background:var(--colorSubtleBackground,transparent)}:host([appearance=subtle]:hover:not([disabled])){background:var(--colorSubtleBackgroundHover,var(--spui-ui-surface-hover,#f5f5f5))}:host([appearance=subtle]:active:not([disabled])){background:var(--colorSubtleBackgroundPressed,var(--spui-ui-surface-hover,#e0e0e0))}:host([appearance=subtle-circular]){border-color:var(--colorTransparentStroke,transparent);background:var(--colorSubtleBackground,transparent)}:host([appearance=subtle-circular]:hover:not([disabled])){border-color:var(--colorNeutralStroke1Hover,var(--spui-ui-stroke,#d1d1d1));background:var(--colorSubtleBackgroundHover,var(--spui-ui-surface-hover,#f5f5f5))}:host([appearance=subtle-circular][aria-selected=true]){border-color:var(--colorCompoundBrandStroke,color-mix(in srgb,var(--spui-ui-accent,#78716c) 52%,var(--spui-base-surface,var(--spui-ui-surface,#fff))));background:var(--colorBrandBackground2,color-mix(in srgb,var(--spui-ui-accent,#78716c) 18%,var(--spui-base-surface,var(--spui-ui-surface,#fff))));color:var(--colorBrandForeground2,var(--spui-ui-accent,#78716c))}:host([appearance=filled-circular]){border-color:transparent;background:var(--colorNeutralBackground3,var(--spui-ui-surface-hover,#f0f0f0))}:host([appearance=filled-circular][aria-selected=true]){border-color:transparent;background:var(--colorBrandBackground,var(--spui-ui-accent,#78716c));color:var(--colorNeutralForegroundOnBrand,#fff)}:host([appearance=subtle-circular])::after,:host([appearance=filled-circular])::after{display:none}@media(forced-colors:active){:host([appearance=subtle-circular][aria-selected=true]){border-color:Highlight}:host([appearance=filled-circular][aria-selected=true]){border-color:ButtonText;background:ButtonText;color:ButtonFace}}`;
|
|
37
|
+
const content = document.createElement("span");
|
|
38
|
+
content.className = "content";
|
|
39
|
+
const textSlot = document.createElement("slot");
|
|
40
|
+
textSlot.name = "content";
|
|
41
|
+
const fallbackSlot = document.createElement("slot");
|
|
42
|
+
textSlot.append(fallbackSlot);
|
|
43
|
+
textSlot.addEventListener("slotchange", () => this.sync());
|
|
44
|
+
fallbackSlot.addEventListener("slotchange", () => this.sync());
|
|
45
|
+
content.append(textSlot);
|
|
46
|
+
const reserved = document.createElement("span");
|
|
47
|
+
reserved.className = "reserved";
|
|
48
|
+
reserved.setAttribute("aria-hidden", "true");
|
|
49
|
+
content.append(reserved);
|
|
50
|
+
const iconSlot = document.createElement("slot");
|
|
51
|
+
iconSlot.name = "icon";
|
|
52
|
+
shadow.append(style, iconSlot, content);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
customElements.define("spui-tab", SpuiTab);
|
|
56
|
+
class SpuiTabList extends HTMLElement {
|
|
57
|
+
static observedAttributes = ["selected-value", "default-selected-value", "select-on-focus", "reserve-selected-tab-space", "vertical", "disabled", "appearance", "size", "aria-label"];
|
|
58
|
+
events;
|
|
59
|
+
observer;
|
|
60
|
+
connectedCallback() {
|
|
61
|
+
if (!this.shadowRoot)
|
|
62
|
+
this.render();
|
|
63
|
+
this.observer = new MutationObserver(() => this.sync());
|
|
64
|
+
this.observer.observe(this, { childList: true, subtree: true, attributes: true, attributeFilter: ["disabled", "value"] });
|
|
65
|
+
this.events?.abort();
|
|
66
|
+
this.events = new AbortController();
|
|
67
|
+
this.addEventListener("click", this.onClick, { signal: this.events.signal });
|
|
68
|
+
this.addEventListener("keydown", this.onKeyDown, { signal: this.events.signal });
|
|
69
|
+
this.addEventListener("focusin", this.onFocusIn, { signal: this.events.signal });
|
|
70
|
+
queueMicrotask(() => this.sync());
|
|
71
|
+
}
|
|
72
|
+
disconnectedCallback() { this.events?.abort(); this.observer?.disconnect(); }
|
|
73
|
+
attributeChangedCallback() { if (this.isConnected)
|
|
74
|
+
this.sync(); }
|
|
75
|
+
get selectedValue() {
|
|
76
|
+
return this.getAttribute("selected-value") ?? this.dataset.selectedValue ?? this.getAttribute("default-selected-value") ?? this.tabs().find(tab => !tab.disabled)?.value ?? null;
|
|
77
|
+
}
|
|
78
|
+
tabs() { return [...this.children].filter((child) => child.localName === "spui-tab"); }
|
|
79
|
+
sync() {
|
|
80
|
+
const tabs = this.tabs();
|
|
81
|
+
const selected = this.selectedValue;
|
|
82
|
+
const focusValue = this.dataset.focusValue;
|
|
83
|
+
const focusTab = tabs.find(tab => tab.value === focusValue && !tab.disabled) ?? tabs.find(tab => tab.value === selected && !tab.disabled) ?? tabs.find(tab => !tab.disabled);
|
|
84
|
+
this.dataset.orientation = this.hasAttribute("vertical") ? "vertical" : "horizontal";
|
|
85
|
+
const role = this.shadowRoot?.querySelector("[role=tablist]");
|
|
86
|
+
role?.setAttribute("aria-orientation", this.hasAttribute("vertical") ? "vertical" : "horizontal");
|
|
87
|
+
const label = this.getAttribute("aria-label");
|
|
88
|
+
if (label)
|
|
89
|
+
role?.setAttribute("aria-label", label);
|
|
90
|
+
else
|
|
91
|
+
role?.removeAttribute("aria-label");
|
|
92
|
+
tabs.forEach(tab => {
|
|
93
|
+
if (this.hasAttribute("disabled"))
|
|
94
|
+
tab.setAttribute("data-list-disabled", "");
|
|
95
|
+
else
|
|
96
|
+
tab.removeAttribute("data-list-disabled");
|
|
97
|
+
if (this.hasAttribute("vertical"))
|
|
98
|
+
tab.setAttribute("vertical", "");
|
|
99
|
+
else
|
|
100
|
+
tab.removeAttribute("vertical");
|
|
101
|
+
tab.setAttribute("selected", String(tab.value === selected));
|
|
102
|
+
tab.setAttribute("tabindex", String(tab === focusTab ? 0 : -1));
|
|
103
|
+
if (this.hasAttribute("appearance"))
|
|
104
|
+
tab.setAttribute("appearance", this.getAttribute("appearance"));
|
|
105
|
+
else
|
|
106
|
+
tab.removeAttribute("appearance");
|
|
107
|
+
if (this.hasAttribute("size"))
|
|
108
|
+
tab.setAttribute("size", this.getAttribute("size"));
|
|
109
|
+
else
|
|
110
|
+
tab.removeAttribute("size");
|
|
111
|
+
const reserveSpace = this.getAttribute("reserve-selected-tab-space") !== "false";
|
|
112
|
+
if (reserveSpace)
|
|
113
|
+
tab.setAttribute("reserve-selected-tab-space", "");
|
|
114
|
+
else
|
|
115
|
+
tab.removeAttribute("reserve-selected-tab-space");
|
|
116
|
+
const reserved = tab.shadowRoot?.querySelector(".reserved");
|
|
117
|
+
if (reserved)
|
|
118
|
+
reserved.textContent = reserveSpace ? tab.getContentText() : "";
|
|
119
|
+
});
|
|
120
|
+
if (selected !== null && this.dataset.lastEmittedValue !== selected) {
|
|
121
|
+
this.dataset.lastEmittedValue = selected;
|
|
122
|
+
this.dispatchEvent(new CustomEvent("spui-tab-selection-change", { bubbles: true, composed: true, detail: { value: selected } }));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
onFocusIn = (event) => {
|
|
126
|
+
const tab = event.composedPath().find(target => target instanceof SpuiTab);
|
|
127
|
+
if (!tab || tab.disabled)
|
|
128
|
+
return;
|
|
129
|
+
this.dataset.focusValue = tab.value;
|
|
130
|
+
this.sync();
|
|
131
|
+
if (this.hasAttribute("select-on-focus"))
|
|
132
|
+
this.select(tab, event);
|
|
133
|
+
};
|
|
134
|
+
onClick = (event) => {
|
|
135
|
+
const tab = event.composedPath().find(target => target instanceof SpuiTab);
|
|
136
|
+
if (tab)
|
|
137
|
+
this.select(tab, event);
|
|
138
|
+
};
|
|
139
|
+
onKeyDown = (event) => {
|
|
140
|
+
const tabs = this.tabs().filter(tab => !tab.disabled);
|
|
141
|
+
const current = event.composedPath().find(target => target instanceof SpuiTab);
|
|
142
|
+
if (!current || !tabs.length)
|
|
143
|
+
return;
|
|
144
|
+
const index = tabs.indexOf(current);
|
|
145
|
+
const vertical = this.hasAttribute("vertical");
|
|
146
|
+
const rtl = getComputedStyle(this).direction === "rtl";
|
|
147
|
+
const nextKey = vertical ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight";
|
|
148
|
+
const previousKey = vertical ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
|
|
149
|
+
let next;
|
|
150
|
+
if (event.key === nextKey)
|
|
151
|
+
next = tabs[(index + 1) % tabs.length];
|
|
152
|
+
else if (event.key === previousKey)
|
|
153
|
+
next = tabs[(index - 1 + tabs.length) % tabs.length];
|
|
154
|
+
else if (event.key === "Home")
|
|
155
|
+
next = tabs[0];
|
|
156
|
+
else if (event.key === "End")
|
|
157
|
+
next = tabs.at(-1);
|
|
158
|
+
else if (event.key === "Enter" || event.key === " ") {
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
this.select(current, event);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!next)
|
|
164
|
+
return;
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
this.dataset.focusValue = next.value;
|
|
167
|
+
this.sync();
|
|
168
|
+
next.focus();
|
|
169
|
+
};
|
|
170
|
+
select(tab, sourceEvent) {
|
|
171
|
+
if (tab.disabled || this.hasAttribute("disabled"))
|
|
172
|
+
return;
|
|
173
|
+
if (!this.hasAttribute("selected-value"))
|
|
174
|
+
this.dataset.selectedValue = tab.value;
|
|
175
|
+
this.dataset.focusValue = tab.value;
|
|
176
|
+
this.sync();
|
|
177
|
+
this.dispatchEvent(new CustomEvent("spui-tab-select", { bubbles: true, composed: true, detail: { value: tab.value, valueType: tab.getAttribute("data-spui-tab-value-type") ?? undefined, sourceEvent, currentTarget: tab } }));
|
|
178
|
+
}
|
|
179
|
+
render() {
|
|
180
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
181
|
+
const style = document.createElement("style");
|
|
182
|
+
style.textContent = `${base}:host{display:block;min-width:0;--spui-tab-gap:4px}.list{display:flex;align-items:center;gap:var(--spui-tab-gap);min-width:0;overflow:auto;scrollbar-width:thin;border-block-end:1px solid var(--spui-tabs-divider,var(--spui-ui-stroke,#d1d1d1))}:host([vertical]) .list{flex-direction:column;align-items:stretch;border-block-end:0;border-inline-end:1px solid var(--spui-tabs-divider,var(--spui-ui-stroke,#d1d1d1))}::slotted(spui-tab){flex:none}@media(forced-colors:active){.list{border-color:CanvasText}}`;
|
|
183
|
+
const list = document.createElement("div");
|
|
184
|
+
list.className = "list";
|
|
185
|
+
list.setAttribute("role", "tablist");
|
|
186
|
+
list.append(document.createElement("slot"));
|
|
187
|
+
shadow.append(style, list);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
customElements.define("spui-tab-list", SpuiTabList);
|
|
191
|
+
class SpuiTabPanel extends HTMLElement {
|
|
192
|
+
static observedAttributes = ["selected", "labelledby"];
|
|
193
|
+
connectedCallback() { if (!this.shadowRoot)
|
|
194
|
+
this.render(); this.sync(); }
|
|
195
|
+
attributeChangedCallback() { if (this.isConnected)
|
|
196
|
+
this.sync(); }
|
|
197
|
+
get value() { return valueOf(this); }
|
|
198
|
+
sync() {
|
|
199
|
+
const selected = this.getAttribute("selected") === "true";
|
|
200
|
+
this.setAttribute("role", "tabpanel");
|
|
201
|
+
this.hidden = !selected;
|
|
202
|
+
this.setAttribute("tabindex", "0");
|
|
203
|
+
const labelledBy = this.getAttribute("labelledby");
|
|
204
|
+
if (labelledBy)
|
|
205
|
+
this.setAttribute("aria-labelledby", labelledBy);
|
|
206
|
+
else
|
|
207
|
+
this.removeAttribute("aria-labelledby");
|
|
208
|
+
}
|
|
209
|
+
render() {
|
|
210
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
211
|
+
const style = document.createElement("style");
|
|
212
|
+
style.textContent = `${base}:host{display:block;min-width:0;padding-block:16px;color:var(--spui-ui-text,var(--spui-semantic-body-text,#242424))}:host([hidden]){display:none}:host(:focus-visible){outline:2px solid var(--spui-focus-ring,var(--spui-semantic-focus-border,#78716c));outline-offset:2px}@media(forced-colors:active){:host(:focus-visible){outline-color:Highlight}}`;
|
|
213
|
+
shadow.append(style, document.createElement("slot"));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
customElements.define("spui-tab-panel", SpuiTabPanel);
|
|
217
|
+
class SpuiTabs extends HTMLElement {
|
|
218
|
+
static observedAttributes = ["selected-value", "default-selected-value", "disabled"];
|
|
219
|
+
observer;
|
|
220
|
+
events;
|
|
221
|
+
connectedCallback() {
|
|
222
|
+
if (!this.shadowRoot)
|
|
223
|
+
this.render();
|
|
224
|
+
this.observer = new MutationObserver(() => this.sync());
|
|
225
|
+
this.observer.observe(this, { childList: true, subtree: true, attributes: true, attributeFilter: ["value", "selected-value", "default-selected-value", "disabled"] });
|
|
226
|
+
this.events?.abort();
|
|
227
|
+
this.events = new AbortController();
|
|
228
|
+
this.addEventListener("spui-tab-selection-change", this.sync, { signal: this.events.signal });
|
|
229
|
+
queueMicrotask(() => this.sync());
|
|
230
|
+
}
|
|
231
|
+
disconnectedCallback() { this.observer?.disconnect(); this.events?.abort(); }
|
|
232
|
+
attributeChangedCallback() { if (this.isConnected)
|
|
233
|
+
this.sync(); }
|
|
234
|
+
sync = () => {
|
|
235
|
+
const list = [...this.querySelectorAll("spui-tab-list")].find(item => item.closest("spui-tabs") === this);
|
|
236
|
+
if (!list)
|
|
237
|
+
return;
|
|
238
|
+
if (this.hasAttribute("disabled")) {
|
|
239
|
+
if (!list.hasAttribute("disabled"))
|
|
240
|
+
list.setAttribute("disabled", "");
|
|
241
|
+
}
|
|
242
|
+
else if (list.hasAttribute("disabled"))
|
|
243
|
+
list.removeAttribute("disabled");
|
|
244
|
+
if (this.hasAttribute("selected-value")) {
|
|
245
|
+
const selected = this.getAttribute("selected-value");
|
|
246
|
+
list.dataset.rootSelectedValue = selected;
|
|
247
|
+
if (list.getAttribute("selected-value") !== selected)
|
|
248
|
+
list.setAttribute("selected-value", selected);
|
|
249
|
+
}
|
|
250
|
+
else if (list.hasAttribute("data-root-selected-value")) {
|
|
251
|
+
delete list.dataset.rootSelectedValue;
|
|
252
|
+
list.removeAttribute("selected-value");
|
|
253
|
+
}
|
|
254
|
+
else if (this.hasAttribute("default-selected-value") && !list.hasAttribute("default-selected-value"))
|
|
255
|
+
list.setAttribute("default-selected-value", this.getAttribute("default-selected-value"));
|
|
256
|
+
const selected = this.getAttribute("selected-value") ?? list.selectedValue ?? this.getAttribute("default-selected-value");
|
|
257
|
+
const tabs = [...this.querySelectorAll("spui-tab")].filter(tab => tab.closest("spui-tab-list") === list);
|
|
258
|
+
const panels = [...this.querySelectorAll("spui-tab-panel")].filter(panel => panel.closest("spui-tabs") === this);
|
|
259
|
+
for (const tab of tabs) {
|
|
260
|
+
const panel = panels.find(candidate => candidate.value === tab.value);
|
|
261
|
+
if (!tab.id)
|
|
262
|
+
tab.id = `spui-tab-${crypto.randomUUID()}`;
|
|
263
|
+
if (panel) {
|
|
264
|
+
if (!panel.id)
|
|
265
|
+
panel.id = `spui-tabpanel-${crypto.randomUUID()}`;
|
|
266
|
+
tab.setAttribute("aria-controls", panel.id);
|
|
267
|
+
panel.setAttribute("labelledby", tab.id);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const panel of panels)
|
|
271
|
+
panel.setAttribute("selected", String(panel.value === selected));
|
|
272
|
+
};
|
|
273
|
+
render() { const shadow = this.attachShadow({ mode: "open" }); const style = document.createElement("style"); style.textContent = `${base}:host{display:block;min-width:0;color:var(--spui-ui-text,var(--spui-semantic-body-text,#242424))}`; shadow.append(style, document.createElement("slot")); }
|
|
274
|
+
}
|
|
275
|
+
customElements.define("spui-tabs", SpuiTabs);
|
|
276
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type TagPickerOpenChangeType = "click" | "keydown" | "input" | "focus" | "focusout" | "blur" | "pointerdown" | "programmatic";
|
|
2
|
+
export type TagPickerOnOpenChangeData = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
reason: string;
|
|
5
|
+
} & ({
|
|
6
|
+
event: MouseEvent;
|
|
7
|
+
type: "click";
|
|
8
|
+
} | {
|
|
9
|
+
event: KeyboardEvent;
|
|
10
|
+
type: "keydown";
|
|
11
|
+
} | {
|
|
12
|
+
event: Event;
|
|
13
|
+
type: Exclude<TagPickerOpenChangeType, "click" | "keydown" | "programmatic">;
|
|
14
|
+
} | {
|
|
15
|
+
event: Event;
|
|
16
|
+
type: "programmatic";
|
|
17
|
+
});
|
|
18
|
+
export type TagPickerOnOptionSelectData = {
|
|
19
|
+
value: string;
|
|
20
|
+
selectedOptions: string[];
|
|
21
|
+
} & ({
|
|
22
|
+
event: MouseEvent;
|
|
23
|
+
type: "click";
|
|
24
|
+
} | {
|
|
25
|
+
event: KeyboardEvent;
|
|
26
|
+
type: "keydown";
|
|
27
|
+
});
|
|
28
|
+
/** Searchable multi-select picker with native form association and light-DOM options. */
|
|
29
|
+
export declare function defineTagPicker(): void;
|