ooxml-ui 0.1.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.
@@ -0,0 +1,99 @@
1
+ import { definer, attachStyles, controlCss } from './chunk-EY266IK3.js';
2
+
3
+ // src/icons.ts
4
+ var registry = /* @__PURE__ */ new Map();
5
+ var BUILT_IN = {
6
+ check: "m3 10 4.5 4.5L17 5",
7
+ close: "M5 5l10 10M15 5 5 15",
8
+ chevronDown: "m5 8 5 5 5-5",
9
+ chevronLeft: "m12 4-6 6 6 6",
10
+ chevronRight: "m8 4 6 6-6 6",
11
+ copy: "M7 7h10v10H7ZM3 13V3h10",
12
+ trash: "M4 6h12M8 6V4h4v2M6 6l.8 10h6.4L14 6",
13
+ lock: "M4 9h12v8H4ZM7 9V6.5a3 3 0 0 1 6 0V9",
14
+ eyeOff: "M2.5 10S5.5 5 10 5s7.5 5 7.5 5-3 5-7.5 5-7.5-5-7.5-5zM4 4l12 12",
15
+ message: "M3 4h14v9H8l-5 4z",
16
+ pencil: "M12 3l5 5M3 17l1-5L13 3l4 4-9 9Z",
17
+ help: "M3 10a7 7 0 1 0 14 0 7 7 0 1 0-14 0M8 7a2 2 0 1 1 3 1.8c-.7.3-1 .8-1 1.7M10 14v.1",
18
+ settings: "M3 5h14M6 10h8M8 15h4M6 3v4M12 8v4M10 13v4",
19
+ clock: "M3 10a7 7 0 1 0 14 0 7 7 0 1 0-14 0M10 6v4l3 2",
20
+ reset: "M4 10a6 6 0 1 1 1.8 4.2M4 6v4h4",
21
+ search: "M3 8.5a5.5 5.5 0 1 0 11 0 5.5 5.5 0 1 0-11 0M13 13l4 4"
22
+ };
23
+ for (const [name, d] of Object.entries(BUILT_IN)) registry.set(name, { d });
24
+ function registerIcon(name, icon) {
25
+ const value = typeof icon === "string" ? { d: icon } : icon;
26
+ if (!name || !value.d) return false;
27
+ registry.set(name, value);
28
+ return true;
29
+ }
30
+ function getIcon(name) {
31
+ return name ? registry.get(name) : void 0;
32
+ }
33
+ function listIcons() {
34
+ return [...registry.keys()].sort();
35
+ }
36
+ var SVG_NS = "http://www.w3.org/2000/svg";
37
+ function createIconSvg(doc) {
38
+ const svg = doc.createElementNS(SVG_NS, "svg");
39
+ svg.setAttribute("aria-hidden", "true");
40
+ return svg;
41
+ }
42
+ function paintIcon(svg, name) {
43
+ const icon = getIcon(name);
44
+ svg.setAttribute("viewBox", icon?.viewBox ?? "0 0 20 20");
45
+ svg.replaceChildren();
46
+ if (!icon) return false;
47
+ const path = svg.ownerDocument.createElementNS(SVG_NS, "path");
48
+ path.setAttribute("d", icon.d);
49
+ svg.append(path);
50
+ return true;
51
+ }
52
+ var ICON_CSS = `
53
+ svg { width: 1em; height: 1em; fill: none; stroke: currentColor; stroke-width: 1.5;
54
+ stroke-linecap: round; stroke-linejoin: round; }
55
+ `;
56
+ var defineIcon = definer("office-ui-icon", () => {
57
+ class OfficeUiIcon extends HTMLElement {
58
+ static observedAttributes = ["name", "label"];
59
+ svg;
60
+ constructor() {
61
+ super();
62
+ const root = this.attachShadow({ mode: "open" });
63
+ attachStyles(
64
+ root,
65
+ controlCss(`:host { display: inline-flex; width: 1em; height: 1em; } ${ICON_CSS}`)
66
+ );
67
+ this.svg = createIconSvg(this.ownerDocument);
68
+ root.append(this.svg);
69
+ }
70
+ connectedCallback() {
71
+ this.sync();
72
+ }
73
+ attributeChangedCallback() {
74
+ this.sync();
75
+ }
76
+ get name() {
77
+ return this.getAttribute("name") ?? "";
78
+ }
79
+ set name(value) {
80
+ this.setAttribute("name", value);
81
+ }
82
+ sync() {
83
+ paintIcon(this.svg, this.name);
84
+ const label = this.getAttribute("label");
85
+ if (label) {
86
+ this.setAttribute("role", "img");
87
+ this.setAttribute("aria-label", label);
88
+ this.removeAttribute("aria-hidden");
89
+ } else {
90
+ this.removeAttribute("role");
91
+ this.removeAttribute("aria-label");
92
+ this.setAttribute("aria-hidden", "true");
93
+ }
94
+ }
95
+ }
96
+ return OfficeUiIcon;
97
+ });
98
+
99
+ export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon };
@@ -0,0 +1,73 @@
1
+ // src/registry.ts
2
+ var CONTRACT_REVISION = 1;
3
+ var CONTRACT_KEY = /* @__PURE__ */ Symbol.for("office-ui.web-control-contract");
4
+ function assertContract(registry, tag) {
5
+ const revision = registry.get(tag)?.[CONTRACT_KEY];
6
+ if (revision !== void 0 && revision !== CONTRACT_REVISION) {
7
+ throw new Error(
8
+ `Incompatible ${tag} contract (${revision}; expected ${CONTRACT_REVISION}). Load one version of ooxml-ui per window.`
9
+ );
10
+ }
11
+ }
12
+ function defineOnce(registry, tag, make) {
13
+ assertContract(registry, tag);
14
+ if (registry.get(tag)) return;
15
+ const ctor = make();
16
+ Object.defineProperty(ctor, CONTRACT_KEY, { value: CONTRACT_REVISION });
17
+ registry.define(tag, ctor);
18
+ }
19
+ function browserRegistry() {
20
+ return typeof window === "undefined" ? void 0 : window.customElements;
21
+ }
22
+ function definer(tag, make) {
23
+ const define = (registry = browserRegistry()) => {
24
+ if (registry) defineOnce(registry, tag, make);
25
+ };
26
+ return Object.assign(define, { tag });
27
+ }
28
+ function emit(host, type, detail, cancelable = false) {
29
+ return host.dispatchEvent(
30
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable })
31
+ );
32
+ }
33
+
34
+ // src/styles.ts
35
+ var sheetsByDocument = /* @__PURE__ */ new WeakMap();
36
+ function attachStyles(root, css) {
37
+ const doc = root.host.ownerDocument;
38
+ const Sheet = doc.defaultView?.CSSStyleSheet;
39
+ if (Sheet && "adoptedStyleSheets" in root && "replaceSync" in Sheet.prototype) {
40
+ try {
41
+ let sheets = sheetsByDocument.get(doc);
42
+ if (!sheets) {
43
+ sheets = /* @__PURE__ */ new Map();
44
+ sheetsByDocument.set(doc, sheets);
45
+ }
46
+ let sheet = sheets.get(css);
47
+ if (!sheet) {
48
+ sheet = new Sheet();
49
+ sheet.replaceSync(css);
50
+ sheets.set(css, sheet);
51
+ }
52
+ root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
53
+ return;
54
+ } catch {
55
+ }
56
+ }
57
+ const style = doc.createElement("style");
58
+ style.textContent = css;
59
+ root.append(style);
60
+ }
61
+ var COMMON_CONTROL_CSS = `
62
+ :host([hidden]) { display: none !important; }
63
+ @media (pointer: coarse), (max-width: 767px) {
64
+ :host { --office-target-size: 44px; }
65
+ }
66
+ @media (forced-colors: active) {
67
+ :host(:focus-visible), :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
68
+ }
69
+ `;
70
+ var controlCss = (css) => `${COMMON_CONTROL_CSS}
71
+ ${css}`;
72
+
73
+ export { CONTRACT_REVISION, attachStyles, controlCss, definer, emit };
@@ -0,0 +1,61 @@
1
+ // src/theme.ts
2
+ var THEME_STYLE_ID = "office-ui-theme";
3
+ var DARK = `
4
+ --office-foreground: #f9fafb;
5
+ --office-muted-foreground: #9ca3af;
6
+ --office-background: #111827;
7
+ --office-surface: #1f2937;
8
+ --office-border: #374151;
9
+ --office-accent: #6366f1;
10
+ --office-ring: #818cf8;
11
+ --office-danger: #f87171;`;
12
+ var THEME_CSS = `
13
+ :root {
14
+ --office-foreground: #1f2937;
15
+ --office-muted-foreground: #6b7280;
16
+ --office-background: #ffffff;
17
+ --office-surface: #f3f4f6;
18
+ --office-border: #d1d5db;
19
+ --office-accent: #2563eb;
20
+ --office-accent-foreground: #ffffff;
21
+ --office-ring: #2563eb;
22
+ --office-danger: #b91c1c;
23
+ --office-target-size: 28px;
24
+ --office-radius: 4px;
25
+ --office-font: system-ui, -apple-system, "Segoe UI", sans-serif;
26
+ }
27
+ @media (prefers-color-scheme: dark) {
28
+ :root:not([data-office-theme="light"]) {${DARK}
29
+ }
30
+ }
31
+ :root[data-office-theme="dark"] {${DARK}
32
+ }
33
+ @media (pointer: coarse), (max-width: 767px) {
34
+ :root { --office-target-size: 44px; }
35
+ }
36
+ @media (forced-colors: active) {
37
+ :root {
38
+ --office-foreground: CanvasText;
39
+ --office-muted-foreground: GrayText;
40
+ --office-background: Canvas;
41
+ --office-surface: Canvas;
42
+ --office-border: CanvasText;
43
+ --office-accent: Highlight;
44
+ --office-accent-foreground: HighlightText;
45
+ --office-ring: Highlight;
46
+ --office-danger: CanvasText;
47
+ }
48
+ }
49
+ `;
50
+ function installOfficeUiTheme(doc = globalThis.document) {
51
+ if (!doc?.head) return false;
52
+ if (!doc.getElementById(THEME_STYLE_ID)) {
53
+ const style = doc.createElement("style");
54
+ style.id = THEME_STYLE_ID;
55
+ style.textContent = THEME_CSS;
56
+ doc.head.append(style);
57
+ }
58
+ return true;
59
+ }
60
+
61
+ export { THEME_CSS, THEME_STYLE_ID, installOfficeUiTheme };
@@ -0,0 +1,221 @@
1
+ import { definer, attachStyles, controlCss, emit } from './chunk-EY266IK3.js';
2
+ import { normalizeHexColor } from 'ooxml-core/color';
3
+
4
+ var SVG_NS = "http://www.w3.org/2000/svg";
5
+ var EMU_PER_PX = 9525;
6
+ function colorToCss(color, scheme) {
7
+ if (!color) return void 0;
8
+ switch (color.kind) {
9
+ case "srgb":
10
+ return normalizeHexColor(color.value, "#808080");
11
+ case "scheme":
12
+ return scheme[color.value] ? normalizeHexColor(scheme[color.value], "#808080") : "#9ca3af";
13
+ case "system":
14
+ return color.fallback ? normalizeHexColor(color.fallback, "#808080") : void 0;
15
+ case "preset":
16
+ return /^[a-z]+$/i.test(color.value) ? color.value.toLowerCase() : "#808080";
17
+ default:
18
+ return "#9ca3af";
19
+ }
20
+ }
21
+ function fillToCss(fill, scheme, report) {
22
+ if (!fill) return "none";
23
+ if (fill.kind === "none") return "none";
24
+ if (fill.kind === "solid") return colorToCss(fill.color, scheme) ?? "none";
25
+ if (!report.approximatedFills.includes(fill.kind)) report.approximatedFills.push(fill.kind);
26
+ if (fill.kind === "gradient") return colorToCss(fill.stops[0]?.color, scheme) ?? "none";
27
+ return "#d1d5db";
28
+ }
29
+ function pathData(path, widthPx, heightPx) {
30
+ const sx = path.width ? widthPx / path.width : 1;
31
+ const sy = path.height ? heightPx / path.height : 1;
32
+ const x = (v) => +(v * sx).toFixed(2);
33
+ const y = (v) => +(v * sy).toFixed(2);
34
+ const out = [];
35
+ for (const c of path.commands) {
36
+ if (c.op === "M" || c.op === "L") out.push(`${c.op}${x(c.x)} ${y(c.y)}`);
37
+ else if (c.op === "C")
38
+ out.push(`C${x(c.x1)} ${y(c.y1)} ${x(c.x2)} ${y(c.y2)} ${x(c.x)} ${y(c.y)}`);
39
+ else if (c.op === "Q") out.push(`Q${x(c.x1)} ${y(c.y1)} ${x(c.x)} ${y(c.y)}`);
40
+ else if (c.op === "Z") out.push("Z");
41
+ }
42
+ return out.join("");
43
+ }
44
+ var hasArc = (shape) => Boolean(shape.paths?.some((p) => p.commands.some((c) => c.op === "A")));
45
+ function shapeElement(doc, shape, report) {
46
+ const { frame } = shape;
47
+ const w = frame.width / EMU_PER_PX;
48
+ const h = frame.height / EMU_PER_PX;
49
+ if (shape.paths?.length) {
50
+ const el = doc.createElementNS(SVG_NS, "g");
51
+ for (const path of shape.paths) {
52
+ const p = doc.createElementNS(SVG_NS, "path");
53
+ p.setAttribute("d", pathData(path, w, h));
54
+ el.append(p);
55
+ }
56
+ if (hasArc(shape) && !report.approximatedGeometries.includes("custom:arc")) {
57
+ report.approximatedGeometries.push("custom:arc");
58
+ }
59
+ return el;
60
+ }
61
+ if (shape.geometry === "ellipse") {
62
+ const e = doc.createElementNS(SVG_NS, "ellipse");
63
+ e.setAttribute("rx", String(w / 2));
64
+ e.setAttribute("ry", String(h / 2));
65
+ e.setAttribute("cx", String(w / 2));
66
+ e.setAttribute("cy", String(h / 2));
67
+ return e;
68
+ }
69
+ const rect = doc.createElementNS(SVG_NS, "rect");
70
+ rect.setAttribute("width", String(w));
71
+ rect.setAttribute("height", String(h));
72
+ if (shape.geometry === "roundRect") {
73
+ const adj = (shape.adjustments?.adj ?? 16667) / 1e5;
74
+ rect.setAttribute("rx", String(Math.min(w, h) * adj));
75
+ } else if (shape.geometry !== "rect" && !report.approximatedGeometries.includes(shape.geometry)) {
76
+ report.approximatedGeometries.push(shape.geometry);
77
+ rect.setAttribute("data-approximated", shape.geometry);
78
+ }
79
+ return rect;
80
+ }
81
+ function renderDiagramDrawing(doc, drawing, scheme = {}) {
82
+ const report = {
83
+ shapeCount: drawing.shapes.length,
84
+ approximatedGeometries: [],
85
+ flattened3d: 0,
86
+ approximatedFills: []
87
+ };
88
+ const svg = doc.createElementNS(SVG_NS, "svg");
89
+ let right = 0;
90
+ let bottom = 0;
91
+ for (const shape of drawing.shapes) {
92
+ const { frame } = shape;
93
+ right = Math.max(right, (frame.x + frame.width) / EMU_PER_PX);
94
+ bottom = Math.max(bottom, (frame.y + frame.height) / EMU_PER_PX);
95
+ const group = doc.createElementNS(SVG_NS, "g");
96
+ group.dataset.modelId = shape.modelId;
97
+ const cx = frame.width / EMU_PER_PX / 2;
98
+ const cy = frame.height / EMU_PER_PX / 2;
99
+ const fx = shape.flipHorizontal ? -1 : 1;
100
+ const fy = shape.flipVertical ? -1 : 1;
101
+ const transform = [`translate(${frame.x / EMU_PER_PX} ${frame.y / EMU_PER_PX})`];
102
+ if (shape.rotation) transform.push(`rotate(${shape.rotation} ${cx} ${cy})`);
103
+ if (fx !== 1 || fy !== 1) {
104
+ transform.push(`translate(${cx} ${cy}) scale(${fx} ${fy}) translate(${-cx} ${-cy})`);
105
+ }
106
+ group.setAttribute("transform", transform.join(" "));
107
+ const body = shapeElement(doc, shape, report);
108
+ body.setAttribute("fill", fillToCss(shape.fill, scheme, report));
109
+ const stroke = shape.line?.fill?.kind === "solid" ? colorToCss(shape.line.fill.color, scheme) : void 0;
110
+ body.setAttribute("stroke", stroke ?? "none");
111
+ if (stroke)
112
+ body.setAttribute(
113
+ "stroke-width",
114
+ String(Math.max(0.5, (shape.line?.widthEmu ?? 9525) / EMU_PER_PX))
115
+ );
116
+ group.append(body);
117
+ if (shape.has3d) {
118
+ report.flattened3d += 1;
119
+ group.setAttribute("data-flattened-3d", "");
120
+ }
121
+ if (shape.text?.text) group.append(textElement(doc, shape, scheme));
122
+ svg.append(group);
123
+ }
124
+ svg.setAttribute("viewBox", `0 0 ${Math.max(right, 1)} ${Math.max(bottom, 1)}`);
125
+ return { svg, report };
126
+ }
127
+ function textElement(doc, shape, scheme) {
128
+ const frame = shape.textFrame ?? {
129
+ x: shape.frame.x,
130
+ y: shape.frame.y,
131
+ width: shape.frame.width,
132
+ height: shape.frame.height
133
+ };
134
+ const ox = (frame.x - shape.frame.x) / EMU_PER_PX;
135
+ const oy = (frame.y - shape.frame.y) / EMU_PER_PX;
136
+ const w = frame.width / EMU_PER_PX;
137
+ const h = frame.height / EMU_PER_PX;
138
+ const text = doc.createElementNS(SVG_NS, "text");
139
+ const first = shape.text?.paragraphs[0]?.runs[0];
140
+ const sizePx = (first?.sizePt ?? 12) * 96 / 72;
141
+ text.setAttribute("x", String(ox + w / 2));
142
+ text.setAttribute("y", String(oy + h / 2));
143
+ text.setAttribute("text-anchor", "middle");
144
+ text.setAttribute("dominant-baseline", "central");
145
+ text.setAttribute("font-size", String(sizePx));
146
+ text.setAttribute("fill", colorToCss(first?.color, scheme) ?? "currentColor");
147
+ if (first?.bold) text.setAttribute("font-weight", "700");
148
+ if (first?.italic) text.setAttribute("font-style", "italic");
149
+ if (first?.typeface) text.setAttribute("font-family", first.typeface);
150
+ text.textContent = shape.text?.paragraphs.map((p) => p.runs.map((r) => r.text).join("")).join(" ") ?? "";
151
+ return text;
152
+ }
153
+
154
+ // src/smartart.ts
155
+ var CSS = `
156
+ :host { display: block; }
157
+ svg { display: block; width: 100%; height: auto; max-height: 100%; }
158
+ text { font-family: var(--office-font, system-ui, sans-serif); pointer-events: none; }
159
+ .empty { padding: 8px; font-size: 12px; color: var(--office-muted-foreground, #6b7280); }
160
+ @media (forced-colors: active) { text { fill: CanvasText; } }
161
+ `;
162
+ var defineSmartArt = definer("office-ui-smartart", () => {
163
+ class OfficeUiSmartArt extends HTMLElement {
164
+ static observedAttributes = ["label"];
165
+ model;
166
+ scheme = {};
167
+ lastReport;
168
+ root;
169
+ constructor() {
170
+ super();
171
+ this.root = this.attachShadow({ mode: "open" });
172
+ attachStyles(this.root, controlCss(CSS));
173
+ }
174
+ connectedCallback() {
175
+ this.render();
176
+ }
177
+ attributeChangedCallback() {
178
+ this.render();
179
+ }
180
+ get drawing() {
181
+ return this.model;
182
+ }
183
+ set drawing(next) {
184
+ this.model = next;
185
+ this.render();
186
+ }
187
+ get schemeColors() {
188
+ return this.scheme;
189
+ }
190
+ set schemeColors(next) {
191
+ this.scheme = next;
192
+ this.render();
193
+ }
194
+ /** The report of the last render (undefined while empty). */
195
+ get report() {
196
+ return this.lastReport;
197
+ }
198
+ render() {
199
+ const doc = this.ownerDocument;
200
+ for (const el of this.root.querySelectorAll("svg, .empty")) el.remove();
201
+ const label = this.getAttribute("label") ?? "Diagram";
202
+ if (!this.model || this.model.shapes.length === 0) {
203
+ this.lastReport = void 0;
204
+ const empty = doc.createElement("div");
205
+ empty.className = "empty";
206
+ empty.textContent = this.model ? "This diagram has no cached drawing to show." : "";
207
+ this.root.append(empty);
208
+ return;
209
+ }
210
+ const { svg, report } = renderDiagramDrawing(doc, this.model, this.scheme);
211
+ svg.setAttribute("role", "img");
212
+ svg.setAttribute("aria-label", label);
213
+ this.root.append(svg);
214
+ this.lastReport = report;
215
+ emit(this, "office-smartart-render", report);
216
+ }
217
+ }
218
+ return OfficeUiSmartArt;
219
+ });
220
+
221
+ export { defineSmartArt, renderDiagramDrawing };
@@ -0,0 +1,118 @@
1
+ import { definer, attachStyles, controlCss, emit } from './chunk-EY266IK3.js';
2
+
3
+ // src/presence.ts
4
+ function participantsFromAwareness(states, selfId) {
5
+ const out = [];
6
+ for (const state of states) {
7
+ const user = state?.user;
8
+ const id = String(user?.id ?? state?.clientId ?? "");
9
+ if (!id || !user?.name) continue;
10
+ out.push({
11
+ id,
12
+ name: user.name,
13
+ ...user.color ? { color: user.color } : {},
14
+ ...user.status ? { status: user.status } : {},
15
+ ...id === selfId || user.self ? { self: true } : {}
16
+ });
17
+ }
18
+ return out;
19
+ }
20
+ var initialsOf = (name) => {
21
+ const parts = name.trim().split(/\s+/).filter(Boolean);
22
+ if (parts.length === 0) return "?";
23
+ const first = [...parts[0] ?? ""][0] ?? "?";
24
+ const last = parts.length > 1 ? [...parts[parts.length - 1] ?? ""][0] ?? "" : "";
25
+ return (first + last).toUpperCase();
26
+ };
27
+ var CSS = `
28
+ :host { display: inline-flex; align-items: center; }
29
+ ul { display: flex; margin: 0; padding: 0; list-style: none; }
30
+ li { margin-inline-start: -6px; }
31
+ li:first-child { margin-inline-start: 0; }
32
+ button, .more { box-sizing: border-box; display: inline-grid; place-items: center;
33
+ width: var(--office-target-size, 28px); height: var(--office-target-size, 28px); padding: 0; border-radius: 50%;
34
+ border: 2px solid var(--office-avatar-color, #6b7280); background: var(--office-surface, #f3f4f6);
35
+ color: var(--office-foreground, #1f2937); font: 600 11px var(--office-font, system-ui, sans-serif); cursor: pointer; }
36
+ .more { cursor: default; border-color: var(--office-border, #d1d5db); }
37
+ button:focus-visible { outline: 2px solid var(--office-ring, #2563eb); outline-offset: 1px; z-index: 1; }
38
+ button[data-status="idle"] { opacity: .7; }
39
+ button[data-status="away"] { opacity: .45; border-style: dashed; }
40
+ @media (forced-colors: active) {
41
+ button, .more { border-color: CanvasText; background: Canvas; color: CanvasText; }
42
+ button:focus-visible { outline-color: Highlight; }
43
+ }
44
+ `;
45
+ var definePresence = definer("office-ui-presence", () => {
46
+ class OfficeUiPresence extends HTMLElement {
47
+ static observedAttributes = ["max", "label"];
48
+ people = [];
49
+ list;
50
+ constructor() {
51
+ super();
52
+ const root = this.attachShadow({ mode: "open" });
53
+ attachStyles(root, controlCss(CSS));
54
+ this.list = this.ownerDocument.createElement("ul");
55
+ this.list.setAttribute("role", "list");
56
+ root.append(this.list);
57
+ }
58
+ connectedCallback() {
59
+ this.render();
60
+ }
61
+ attributeChangedCallback() {
62
+ this.render();
63
+ }
64
+ get participants() {
65
+ return this.people;
66
+ }
67
+ set participants(next) {
68
+ this.people = [...next].sort((a, b) => Number(Boolean(b.self)) - Number(Boolean(a.self)));
69
+ this.render();
70
+ }
71
+ get max() {
72
+ const n = Number(this.getAttribute("max") ?? 4);
73
+ return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 4;
74
+ }
75
+ render() {
76
+ const doc = this.ownerDocument;
77
+ this.setAttribute("role", "group");
78
+ this.setAttribute(
79
+ "aria-label",
80
+ this.getAttribute("label") ?? `${this.people.length} collaborators`
81
+ );
82
+ const shown = this.people.slice(0, this.max);
83
+ const items = shown.map((person) => {
84
+ const li = doc.createElement("li");
85
+ const button = doc.createElement("button");
86
+ button.type = "button";
87
+ button.textContent = initialsOf(person.name);
88
+ button.dataset.id = person.id;
89
+ button.dataset.status = person.status ?? "active";
90
+ if (person.color) button.style.setProperty("--office-avatar-color", person.color);
91
+ const status = person.status && person.status !== "active" ? `, ${person.status}` : "";
92
+ button.setAttribute("aria-label", `${person.name}${person.self ? " (you)" : ""}${status}`);
93
+ button.title = person.name;
94
+ button.addEventListener(
95
+ "click",
96
+ () => emit(this, "office-presence-select", { id: person.id })
97
+ );
98
+ li.append(button);
99
+ return li;
100
+ });
101
+ const hidden = this.people.length - shown.length;
102
+ if (hidden > 0) {
103
+ const li = doc.createElement("li");
104
+ const chip = doc.createElement("span");
105
+ chip.className = "more";
106
+ chip.textContent = `+${hidden}`;
107
+ chip.title = this.people.slice(shown.length).map((p) => p.name).join(", ");
108
+ chip.setAttribute("aria-label", `${hidden} more: ${chip.title}`);
109
+ li.append(chip);
110
+ items.push(li);
111
+ }
112
+ this.list.replaceChildren(...items);
113
+ }
114
+ }
115
+ return OfficeUiPresence;
116
+ });
117
+
118
+ export { definePresence, initialsOf, participantsFromAwareness };
@@ -0,0 +1,16 @@
1
+ import { defineButton } from './button.js';
2
+ import { defineCheckbox, defineSwitch } from './checkable.js';
3
+ import { defineDialog } from './dialog.js';
4
+ import { defineIcon } from './icons.js';
5
+ import { defineRibbonGroup, defineToolbar } from './ribbon.js';
6
+ import { defineSelect } from './select.js';
7
+ import { defineStatusBar, defineStatusItem } from './status-bar.js';
8
+ export { defineButton, defineCheckbox, defineDialog, defineIcon, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineToolbar, };
9
+ export type { OfficeCommandEvent } from './button.js';
10
+ export type { OfficeDialogCloseEvent, OfficeDialogCloseReason } from './dialog.js';
11
+ export type { OfficeSelectOption } from './select.js';
12
+ export type { OfficeStatusActivateEvent } from './status-bar.js';
13
+ /** Every control of this entry. */
14
+ export declare const CONTROL_DEFINERS: readonly [import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer];
15
+ /** Idempotent, SSR-safe (no-op without a DOM). */
16
+ export declare function registerControls(registry?: CustomElementRegistry): void;
@@ -0,0 +1,3 @@
1
+ export { CONTROL_DEFINERS, defineButton, defineCheckbox, defineDialog, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineToolbar, registerControls } from './chunk-5UKGBJXE.js';
2
+ export { defineIcon } from './chunk-7MCSBDSU.js';
3
+ import './chunk-EY266IK3.js';
@@ -0,0 +1,13 @@
1
+ export type OfficeDialogCloseReason = 'escape' | 'close-button' | 'backdrop' | 'api';
2
+ /** Cancelable: `preventDefault()` keeps the dialog open. */
3
+ export type OfficeDialogCloseEvent = CustomEvent<{
4
+ reason: OfficeDialogCloseReason;
5
+ }>;
6
+ /**
7
+ * Modal dialog shell. `open` and `heading` attributes, `dismissible` (default true; set
8
+ * `dismissible="false"` to ignore Escape and backdrop). Default slot is the body, `slot="footer"`
9
+ * holds actions. `role="dialog"`, `aria-modal`, labelled by the heading. Escape, the close button
10
+ * and the backdrop emit a cancelable `office-dialog-close` and close unless prevented; focus
11
+ * moves in on open, is trapped with Tab, and returns to the previously focused element on close.
12
+ */
13
+ export declare const defineDialog: import("./registry.js").Definer;
@@ -0,0 +1,16 @@
1
+ /** A trusted icon: SVG path data in a 20x20 box. Hosts never inject markup. */
2
+ export interface OfficeIcon {
3
+ d: string;
4
+ /** Defaults to `0 0 20 20`. */
5
+ viewBox?: string;
6
+ }
7
+ /** Register or replace an icon. Returns false for an empty name or path. */
8
+ export declare function registerIcon(name: string, icon: OfficeIcon | string): boolean;
9
+ export declare function getIcon(name: string | null | undefined): OfficeIcon | undefined;
10
+ export declare function listIcons(): string[];
11
+ export declare function createIconSvg(doc: Document): SVGSVGElement;
12
+ /** Fill an `<svg>` with a registered icon (empty when unknown). Used by every control. */
13
+ export declare function paintIcon(svg: SVGElement, name: string | null | undefined): boolean;
14
+ export declare const ICON_CSS = "\nsvg { width: 1em; height: 1em; fill: none; stroke: currentColor; stroke-width: 1.5;\n\tstroke-linecap: round; stroke-linejoin: round; }\n";
15
+ /** `<office-ui-icon name="check" label="Done">`: decorative unless `label` is set. */
16
+ export declare const defineIcon: import("./registry.js").Definer;
package/dist/icons.js ADDED
@@ -0,0 +1,2 @@
1
+ export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-7MCSBDSU.js';
2
+ import './chunk-EY266IK3.js';
@@ -0,0 +1,15 @@
1
+ export * from './controls.js';
2
+ export * from './icons.js';
3
+ export * from './presence.js';
4
+ export * from './smartart.js';
5
+ export * from './theme.js';
6
+ export { CONTRACT_REVISION, type Definer } from './registry.js';
7
+ /** Every ooxml-ui tag this package defines, in registration order. */
8
+ export declare const OFFICE_UI_TAGS: readonly ["office-ui-icon", "office-ui-button", "office-ui-checkbox", "office-ui-switch", "office-ui-select", "office-ui-ribbon-group", "office-ui-toolbar", "office-ui-dialog", "office-ui-status-bar", "office-ui-status-item", "office-ui-presence", "office-ui-smartart"];
9
+ /**
10
+ * Install the theme tokens and define every element. Idempotent and safe to call from every
11
+ * viewer binding; a no-op where there is no DOM (SSR).
12
+ */
13
+ export declare function registerOfficeUi(options?: {
14
+ theme?: boolean;
15
+ }): void;