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,745 @@
1
+ import { createIconSvg, paintIcon, defineIcon } from './chunk-7MCSBDSU.js';
2
+ import { definer, attachStyles, controlCss, emit } from './chunk-EY266IK3.js';
3
+
4
+ // src/button.ts
5
+ var CSS = `
6
+ :host { display: inline-flex; vertical-align: middle; }
7
+ button { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
8
+ gap: 6px; min-width: var(--office-target-size, 28px); min-height: var(--office-target-size, 28px);
9
+ padding: 0 8px; border: 1px solid transparent; border-radius: var(--office-radius, 4px);
10
+ background: transparent; color: var(--office-foreground, #1f2937); font: inherit; font-size: 12px;
11
+ cursor: pointer; white-space: nowrap; }
12
+ :host([variant="stacked"]) button { flex-direction: column; padding: 4px 8px; }
13
+ button:hover:not(:disabled) { background: var(--office-surface, #f3f4f6); }
14
+ button:focus-visible { outline: 2px solid var(--office-ring, #2563eb); outline-offset: 1px; }
15
+ button[aria-pressed="true"], button[aria-expanded="true"] { border-color: var(--office-accent, #2563eb);
16
+ background: var(--office-surface, #f3f4f6); }
17
+ button:disabled { opacity: .5; cursor: not-allowed; }
18
+ svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.5;
19
+ stroke-linecap: round; stroke-linejoin: round; }
20
+ svg:not([data-painted]) { display: none; }
21
+ @media (forced-colors: active) {
22
+ button { border-color: ButtonText; color: ButtonText; background: ButtonFace; }
23
+ button:disabled { color: GrayText; border-color: GrayText; }
24
+ button[aria-pressed="true"], button[aria-expanded="true"] { background: Highlight; color: HighlightText; }
25
+ }
26
+ `;
27
+ var defineButton = definer("office-ui-button", () => {
28
+ class OfficeUiButton extends HTMLElement {
29
+ static observedAttributes = [
30
+ "label",
31
+ "icon",
32
+ "command",
33
+ "disabled",
34
+ "pressed",
35
+ "expanded",
36
+ "icon-only",
37
+ "title"
38
+ ];
39
+ button;
40
+ svg;
41
+ text;
42
+ constructor() {
43
+ super();
44
+ const root = this.attachShadow({ mode: "open", delegatesFocus: true });
45
+ attachStyles(root, controlCss(CSS));
46
+ this.button = this.ownerDocument.createElement("button");
47
+ this.button.type = "button";
48
+ this.button.setAttribute("part", "button");
49
+ this.svg = createIconSvg(this.ownerDocument);
50
+ this.text = this.ownerDocument.createElement("span");
51
+ this.button.append(this.svg, this.text);
52
+ root.append(this.button);
53
+ this.button.addEventListener("click", () => {
54
+ const command = this.getAttribute("command");
55
+ if (!this.button.disabled && command) emit(this, "office-command", { command });
56
+ });
57
+ }
58
+ connectedCallback() {
59
+ this.sync();
60
+ }
61
+ attributeChangedCallback() {
62
+ this.sync();
63
+ }
64
+ get disabled() {
65
+ return this.hasAttribute("disabled");
66
+ }
67
+ set disabled(value) {
68
+ this.toggleAttribute("disabled", Boolean(value));
69
+ }
70
+ get pressed() {
71
+ const value = this.getAttribute("pressed");
72
+ return value === null ? void 0 : value !== "false";
73
+ }
74
+ set pressed(value) {
75
+ if (value === void 0) this.removeAttribute("pressed");
76
+ else this.setAttribute("pressed", String(value));
77
+ }
78
+ sync() {
79
+ const label = this.getAttribute("label") ?? "";
80
+ const iconOnly = this.hasAttribute("icon-only");
81
+ this.text.textContent = label;
82
+ this.text.hidden = iconOnly;
83
+ if (iconOnly && label) this.button.setAttribute("aria-label", label);
84
+ else this.button.removeAttribute("aria-label");
85
+ this.button.title = this.getAttribute("title") ?? (iconOnly ? label : "");
86
+ this.button.disabled = this.disabled;
87
+ if (paintIcon(this.svg, this.getAttribute("icon"))) this.svg.setAttribute("data-painted", "");
88
+ else this.svg.removeAttribute("data-painted");
89
+ for (const attr of ["pressed", "expanded"]) {
90
+ const value = this.getAttribute(attr);
91
+ if (value === null) this.button.removeAttribute(`aria-${attr}`);
92
+ else this.button.setAttribute(`aria-${attr}`, value === "false" ? "false" : "true");
93
+ }
94
+ }
95
+ }
96
+ return OfficeUiButton;
97
+ });
98
+
99
+ // src/checkable.ts
100
+ var CHECK_SVG = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m3 8 3.2 3.2L13 4.5"/></svg>';
101
+ var CHECKBOX_CSS = `
102
+ :host { display: inline-grid; box-sizing: border-box; width: 16px; height: 16px; flex: none;
103
+ place-items: center; border: 1px solid var(--office-border, #d1d5db); border-radius: 3px;
104
+ background: var(--office-background, #fff); color: var(--office-accent-foreground, #fff);
105
+ cursor: pointer; vertical-align: middle; }
106
+ :host([checked]) { border-color: var(--office-accent, #2563eb); background: var(--office-accent, #2563eb); }
107
+ :host(:focus-visible) { outline: 2px solid var(--office-ring, #2563eb); outline-offset: 2px; }
108
+ :host([disabled]) { opacity: .5; cursor: not-allowed; }
109
+ svg { display: none; width: 12px; height: 12px; }
110
+ :host([checked]) svg { display: block; }
111
+ @media (pointer: coarse), (max-width: 767px) { :host { width: 22px; height: 22px; } svg { width: 16px; height: 16px; } }
112
+ @media (forced-colors: active) {
113
+ :host { border-color: CanvasText; background: Canvas; color: CanvasText; forced-color-adjust: auto; }
114
+ :host([checked]) { border-color: Highlight; background: Highlight; color: HighlightText; }
115
+ }
116
+ `;
117
+ var SWITCH_CSS = `
118
+ :host { display: inline-block; box-sizing: border-box; position: relative; width: 32px; height: 18px;
119
+ flex: none; border: 1px solid var(--office-border, #d1d5db); border-radius: 9px;
120
+ background: var(--office-surface, #f3f4f6); cursor: pointer; vertical-align: middle; }
121
+ :host::after { content: ""; position: absolute; top: 2px; left: 2px; width: 12px; height: 12px;
122
+ border-radius: 50%; background: var(--office-foreground, #1f2937); transition: transform .12s; }
123
+ :host([checked]) { border-color: var(--office-accent, #2563eb); background: var(--office-accent, #2563eb); }
124
+ :host([checked])::after { transform: translateX(14px); background: var(--office-accent-foreground, #fff); }
125
+ :host(:focus-visible) { outline: 2px solid var(--office-ring, #2563eb); outline-offset: 2px; }
126
+ :host([disabled]) { opacity: .5; cursor: not-allowed; }
127
+ svg { display: none; }
128
+ @media (pointer: coarse), (max-width: 767px) { :host { min-height: 24px; width: 44px; height: 24px; border-radius: 12px; } }
129
+ @media (forced-colors: active) {
130
+ :host { border-color: CanvasText; background: Canvas; forced-color-adjust: none; }
131
+ :host::after { background: CanvasText; }
132
+ :host([checked]) { border-color: Highlight; background: Highlight; }
133
+ :host([checked])::after { background: HighlightText; }
134
+ }
135
+ `;
136
+ function makeCheckable(role, css) {
137
+ return () => class OfficeUiCheckable extends HTMLElement {
138
+ static formAssociated = true;
139
+ static observedAttributes = ["checked", "disabled", "value"];
140
+ internals;
141
+ defaultChecked = false;
142
+ constructor() {
143
+ super();
144
+ try {
145
+ this.internals = this.attachInternals();
146
+ } catch {
147
+ this.internals = void 0;
148
+ }
149
+ const root = this.attachShadow({ mode: "open" });
150
+ attachStyles(root, controlCss(css));
151
+ const template = this.ownerDocument.createElement("template");
152
+ template.innerHTML = CHECK_SVG;
153
+ root.append(template.content.cloneNode(true));
154
+ this.addEventListener("click", (event) => {
155
+ if (this.disabled) event.preventDefault();
156
+ else this.toggle();
157
+ });
158
+ this.addEventListener("keydown", (event) => {
159
+ const key = event.key;
160
+ if (key !== " " && !(role === "switch" && key === "Enter") || this.disabled) return;
161
+ event.preventDefault();
162
+ this.toggle();
163
+ });
164
+ }
165
+ connectedCallback() {
166
+ this.defaultChecked = this.checked;
167
+ this.sync();
168
+ }
169
+ attributeChangedCallback() {
170
+ this.sync();
171
+ }
172
+ get checked() {
173
+ return this.hasAttribute("checked");
174
+ }
175
+ set checked(value) {
176
+ this.toggleAttribute("checked", Boolean(value));
177
+ }
178
+ get disabled() {
179
+ return this.hasAttribute("disabled");
180
+ }
181
+ set disabled(value) {
182
+ this.toggleAttribute("disabled", Boolean(value));
183
+ }
184
+ get value() {
185
+ return this.getAttribute("value") ?? "on";
186
+ }
187
+ set value(value) {
188
+ this.setAttribute("value", String(value));
189
+ }
190
+ formResetCallback() {
191
+ this.checked = this.defaultChecked;
192
+ }
193
+ formDisabledCallback(disabled) {
194
+ this.disabled = disabled;
195
+ }
196
+ toggle() {
197
+ this.checked = !this.checked;
198
+ this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
199
+ this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
200
+ }
201
+ sync() {
202
+ this.setAttribute("role", role);
203
+ this.setAttribute("aria-checked", String(this.checked));
204
+ this.setAttribute("aria-disabled", String(this.disabled));
205
+ this.tabIndex = this.disabled ? -1 : 0;
206
+ this.internals?.setFormValue?.(this.checked && !this.disabled ? this.value : null);
207
+ }
208
+ };
209
+ }
210
+ var defineCheckbox = definer(
211
+ "office-ui-checkbox",
212
+ makeCheckable("checkbox", CHECKBOX_CSS)
213
+ );
214
+ var defineSwitch = definer("office-ui-switch", makeCheckable("switch", SWITCH_CSS));
215
+
216
+ // src/dialog.ts
217
+ var CSS2 = `
218
+ :host { display: none; position: fixed; inset: 0; z-index: 1000; align-items: center; justify-content: center; }
219
+ :host([open]) { display: flex; }
220
+ .backdrop { position: absolute; inset: 0; background: rgb(0 0 0 / .4); }
221
+ .box { position: relative; box-sizing: border-box; display: flex; flex-direction: column;
222
+ min-width: min(320px, 100vw); max-width: min(640px, 100vw); max-height: 90vh;
223
+ background: var(--office-background, #fff); color: var(--office-foreground, #1f2937);
224
+ border: 1px solid var(--office-border, #d1d5db); border-radius: 8px; font-family: var(--office-font, system-ui, sans-serif); }
225
+ header { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 16px;
226
+ border-bottom: 1px solid var(--office-border, #d1d5db); }
227
+ h2 { margin: 0; font-size: 15px; font-weight: 600; }
228
+ .body { padding: 16px; overflow: auto; font-size: 13px; }
229
+ footer { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--office-border, #d1d5db); }
230
+ footer[hidden] { display: none; }
231
+ .close { display: inline-flex; align-items: center; justify-content: center; min-width: var(--office-target-size, 28px);
232
+ min-height: var(--office-target-size, 28px); border: 0; background: transparent; color: inherit; cursor: pointer; border-radius: 4px; }
233
+ .close svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
234
+ .close:focus-visible { outline: 2px solid var(--office-ring, #2563eb); }
235
+ @media (forced-colors: active) { .box { border-color: CanvasText; background: Canvas; } .backdrop { background: transparent; } }
236
+ `;
237
+ var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), office-ui-button:not([disabled]), office-ui-select:not([disabled]), office-ui-checkbox:not([disabled]), office-ui-switch:not([disabled])';
238
+ var defineDialog = definer("office-ui-dialog", () => {
239
+ class OfficeUiDialog extends HTMLElement {
240
+ static observedAttributes = ["open", "heading"];
241
+ box;
242
+ heading;
243
+ closeButton;
244
+ footer;
245
+ returnFocus = null;
246
+ constructor() {
247
+ super();
248
+ const doc = this.ownerDocument;
249
+ const root = this.attachShadow({ mode: "open" });
250
+ attachStyles(root, controlCss(CSS2));
251
+ const backdrop = doc.createElement("div");
252
+ backdrop.className = "backdrop";
253
+ this.box = doc.createElement("div");
254
+ this.box.className = "box";
255
+ this.box.setAttribute("role", "dialog");
256
+ this.box.setAttribute("aria-modal", "true");
257
+ this.box.tabIndex = -1;
258
+ const header = doc.createElement("header");
259
+ this.heading = doc.createElement("h2");
260
+ this.heading.id = "title";
261
+ this.box.setAttribute("aria-labelledby", "title");
262
+ this.closeButton = doc.createElement("button");
263
+ this.closeButton.type = "button";
264
+ this.closeButton.className = "close";
265
+ this.closeButton.setAttribute("aria-label", "Close");
266
+ const icon = createIconSvg(doc);
267
+ paintIcon(icon, "close");
268
+ this.closeButton.append(icon);
269
+ header.append(this.heading, this.closeButton);
270
+ const body = doc.createElement("div");
271
+ body.className = "body";
272
+ body.append(doc.createElement("slot"));
273
+ this.footer = doc.createElement("footer");
274
+ const footerSlot = doc.createElement("slot");
275
+ footerSlot.name = "footer";
276
+ this.footer.append(footerSlot);
277
+ this.box.append(header, body, this.footer);
278
+ root.append(backdrop, this.box);
279
+ backdrop.addEventListener("click", () => this.requestClose("backdrop"));
280
+ this.closeButton.addEventListener("click", () => this.requestClose("close-button"));
281
+ this.addEventListener("keydown", (event) => this.onKey(event));
282
+ }
283
+ attributeChangedCallback(name, old, value) {
284
+ if (name === "heading") this.heading.textContent = value ?? "";
285
+ if (name === "open" && old === null !== (value === null)) {
286
+ if (value !== null) this.onOpen();
287
+ else this.onClose();
288
+ }
289
+ }
290
+ connectedCallback() {
291
+ this.heading.textContent = this.getAttribute("heading") ?? "";
292
+ if (this.open) this.onOpen();
293
+ }
294
+ get open() {
295
+ return this.hasAttribute("open");
296
+ }
297
+ set open(value) {
298
+ this.toggleAttribute("open", Boolean(value));
299
+ }
300
+ show() {
301
+ this.open = true;
302
+ }
303
+ /** Programmatic close; still emits `office-dialog-close` (reason `api`). */
304
+ close() {
305
+ this.requestClose("api");
306
+ }
307
+ get dismissible() {
308
+ return this.getAttribute("dismissible") !== "false";
309
+ }
310
+ requestClose(reason) {
311
+ if ((reason === "escape" || reason === "backdrop") && !this.dismissible) return;
312
+ if (emit(this, "office-dialog-close", { reason }, true)) this.open = false;
313
+ }
314
+ onOpen() {
315
+ const active = this.ownerDocument.activeElement;
316
+ this.returnFocus = active && active !== this.ownerDocument.body ? active : null;
317
+ this.footer.hidden = this.querySelector('[slot="footer"]') === null;
318
+ (this.focusables()[0] ?? this.box).focus();
319
+ }
320
+ onClose() {
321
+ const target = this.returnFocus;
322
+ this.returnFocus = null;
323
+ target?.focus?.();
324
+ }
325
+ focusables() {
326
+ return [...this.querySelectorAll(FOCUSABLE)];
327
+ }
328
+ onKey(event) {
329
+ if (!this.open) return;
330
+ if (event.key === "Escape") {
331
+ event.preventDefault();
332
+ event.stopPropagation();
333
+ this.requestClose("escape");
334
+ } else if (event.key === "Tab") {
335
+ const items = [...this.focusables(), this.closeButton];
336
+ const current = this.ownerDocument.activeElement;
337
+ const inside = (el) => el === current || el.contains(current);
338
+ const index = items.findIndex(inside);
339
+ const shadowFocus = this.shadowRoot?.activeElement === this.closeButton;
340
+ const at = shadowFocus ? items.length - 1 : index;
341
+ const last = items.length - 1;
342
+ if (at < 0 || !event.shiftKey && at === last || event.shiftKey && at === 0) {
343
+ event.preventDefault();
344
+ items[at < 0 || !event.shiftKey ? 0 : last]?.focus();
345
+ }
346
+ }
347
+ }
348
+ }
349
+ return OfficeUiDialog;
350
+ });
351
+
352
+ // src/ribbon.ts
353
+ var GROUP_CSS = `
354
+ :host { display: inline-flex; flex: none; align-self: stretch; }
355
+ .group { box-sizing: border-box; display: flex; flex-direction: column; justify-content: space-between;
356
+ min-height: 78px; padding: 4px 8px 2px;
357
+ border-inline-end: 1px solid color-mix(in srgb, var(--office-border, #d1d5db) 60%, transparent); }
358
+ :host(:last-child) .group { border-inline-end: 0; }
359
+ .row { display: flex; align-items: flex-start; gap: 4px; }
360
+ ::slotted(*) { flex-shrink: 0; }
361
+ .caption { padding-top: 2px; color: var(--office-muted-foreground, #6b7280); font-size: 10px;
362
+ line-height: 12px; text-align: center; white-space: nowrap; }
363
+ @media (forced-colors: active) { .group { border-inline-end-color: CanvasText; } .caption { color: CanvasText; } }
364
+ `;
365
+ var defineRibbonGroup = definer("office-ui-ribbon-group", () => {
366
+ class OfficeUiRibbonGroup extends HTMLElement {
367
+ static observedAttributes = ["label"];
368
+ caption;
369
+ constructor() {
370
+ super();
371
+ const doc = this.ownerDocument;
372
+ const root = this.attachShadow({ mode: "open" });
373
+ attachStyles(root, controlCss(GROUP_CSS));
374
+ const group = doc.createElement("div");
375
+ group.className = "group";
376
+ const row = doc.createElement("div");
377
+ row.className = "row";
378
+ row.append(doc.createElement("slot"));
379
+ this.caption = doc.createElement("span");
380
+ this.caption.className = "caption";
381
+ group.append(row, this.caption);
382
+ root.append(group);
383
+ }
384
+ connectedCallback() {
385
+ this.setAttribute("role", "group");
386
+ this.sync();
387
+ }
388
+ attributeChangedCallback() {
389
+ this.sync();
390
+ }
391
+ sync() {
392
+ const label = this.getAttribute("label") ?? "";
393
+ this.caption.textContent = label;
394
+ this.setAttribute("aria-label", label);
395
+ }
396
+ }
397
+ return OfficeUiRibbonGroup;
398
+ });
399
+ var TOOLBAR_CSS = `
400
+ :host { display: flex; flex-wrap: wrap; align-items: stretch; gap: 2px; padding: 2px 4px;
401
+ background: var(--office-surface, #f3f4f6); border-bottom: 1px solid var(--office-border, #d1d5db); }
402
+ @media (forced-colors: active) { :host { border-bottom-color: CanvasText; background: Canvas; } }
403
+ `;
404
+ var ITEM = "office-ui-button, office-ui-select, office-ui-checkbox, office-ui-switch";
405
+ var defineToolbar = definer("office-ui-toolbar", () => {
406
+ class OfficeUiToolbar extends HTMLElement {
407
+ constructor() {
408
+ super();
409
+ const root = this.attachShadow({ mode: "open" });
410
+ attachStyles(root, controlCss(TOOLBAR_CSS));
411
+ root.append(this.ownerDocument.createElement("slot"));
412
+ this.addEventListener("keydown", (event) => this.onKey(event));
413
+ }
414
+ connectedCallback() {
415
+ this.setAttribute("role", "toolbar");
416
+ }
417
+ items() {
418
+ return [...this.querySelectorAll(ITEM)].filter(
419
+ (el) => !el.hasAttribute("disabled")
420
+ );
421
+ }
422
+ onKey(event) {
423
+ const vertical = this.getAttribute("aria-orientation") === "vertical";
424
+ const next = vertical ? "ArrowDown" : "ArrowRight";
425
+ const prev = vertical ? "ArrowUp" : "ArrowLeft";
426
+ if (![next, prev, "Home", "End"].includes(event.key)) return;
427
+ const items = this.items();
428
+ const from = items.findIndex(
429
+ (el) => el === event.target || el.contains(event.target)
430
+ );
431
+ if (from < 0 || items.length === 0) return;
432
+ event.preventDefault();
433
+ const index = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : (from + (event.key === next ? 1 : -1) + items.length) % items.length;
434
+ items[index]?.focus();
435
+ }
436
+ }
437
+ return OfficeUiToolbar;
438
+ });
439
+
440
+ // src/select.ts
441
+ var CSS3 = `
442
+ :host { display: inline-block; position: relative; vertical-align: middle; }
443
+ button { box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: 8px;
444
+ width: 100%; min-height: var(--office-target-size, 28px); padding: 0 8px; font: inherit; font-size: 12px;
445
+ color: var(--office-foreground, #1f2937); background: var(--office-background, #fff);
446
+ border: 1px solid var(--office-border, #d1d5db); border-radius: var(--office-radius, 4px); cursor: pointer; }
447
+ button:focus-visible { outline: 2px solid var(--office-ring, #2563eb); outline-offset: 1px; }
448
+ :host([disabled]) button { opacity: .5; cursor: not-allowed; }
449
+ ul { position: absolute; z-index: 10; inset-inline: 0; top: 100%; margin: 2px 0 0; padding: 2px; list-style: none;
450
+ max-height: 240px; overflow: auto; background: var(--office-background, #fff);
451
+ border: 1px solid var(--office-border, #d1d5db); border-radius: var(--office-radius, 4px); }
452
+ ul[hidden] { display: none; }
453
+ li { display: flex; align-items: center; min-height: var(--office-target-size, 28px); padding: 0 8px;
454
+ font-size: 12px; color: var(--office-foreground, #1f2937); cursor: pointer; border-radius: 3px; }
455
+ li[aria-selected="true"] { font-weight: 600; }
456
+ li[data-active] { background: var(--office-surface, #f3f4f6); }
457
+ li[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
458
+ @media (forced-colors: active) {
459
+ button, ul { border-color: CanvasText; background: Canvas; color: CanvasText; }
460
+ li[data-active] { background: Highlight; color: HighlightText; }
461
+ }
462
+ `;
463
+ var uid = 0;
464
+ var defineSelect = definer("office-ui-select", () => {
465
+ class OfficeUiSelect extends HTMLElement {
466
+ static observedAttributes = ["value", "disabled", "aria-label"];
467
+ opts = [];
468
+ current = "";
469
+ active = -1;
470
+ typed = "";
471
+ typedAt = 0;
472
+ trigger;
473
+ list;
474
+ label;
475
+ domId = `office-ui-select-${++uid}`;
476
+ constructor() {
477
+ super();
478
+ const doc = this.ownerDocument;
479
+ const root = this.attachShadow({ mode: "open", delegatesFocus: true });
480
+ attachStyles(root, controlCss(CSS3));
481
+ this.trigger = doc.createElement("button");
482
+ this.trigger.type = "button";
483
+ this.trigger.setAttribute("role", "combobox");
484
+ this.trigger.setAttribute("aria-haspopup", "listbox");
485
+ this.trigger.setAttribute("aria-expanded", "false");
486
+ this.trigger.setAttribute("aria-controls", this.domId);
487
+ this.label = doc.createElement("span");
488
+ const caret = doc.createElement("span");
489
+ caret.setAttribute("aria-hidden", "true");
490
+ caret.textContent = "\u25BE";
491
+ this.trigger.append(this.label, caret);
492
+ this.list = doc.createElement("ul");
493
+ this.list.id = this.domId;
494
+ this.list.setAttribute("role", "listbox");
495
+ this.list.hidden = true;
496
+ root.append(this.trigger, this.list);
497
+ this.trigger.addEventListener("click", () => this.isOpen ? this.close() : this.open());
498
+ this.trigger.addEventListener("keydown", (event) => this.onKey(event));
499
+ this.trigger.addEventListener("blur", () => this.close());
500
+ this.list.addEventListener("mousedown", (event) => event.preventDefault());
501
+ this.list.addEventListener("click", (event) => {
502
+ const item = event.target.closest("li");
503
+ if (item) this.choose(Number(item.dataset.index));
504
+ });
505
+ }
506
+ connectedCallback() {
507
+ this.sync();
508
+ }
509
+ attributeChangedCallback(name, _old, value) {
510
+ if (name === "value") this.current = value ?? "";
511
+ this.sync();
512
+ }
513
+ get options() {
514
+ return this.opts;
515
+ }
516
+ set options(next) {
517
+ this.opts = next.map((option) => ({ ...option }));
518
+ this.sync();
519
+ }
520
+ get value() {
521
+ return this.current;
522
+ }
523
+ set value(next) {
524
+ this.current = String(next);
525
+ this.sync();
526
+ }
527
+ get selectedIndex() {
528
+ return this.opts.findIndex((option) => option.value === this.current);
529
+ }
530
+ set selectedIndex(index) {
531
+ const option = this.opts[index];
532
+ if (option) this.value = option.value;
533
+ }
534
+ get disabled() {
535
+ return this.hasAttribute("disabled");
536
+ }
537
+ set disabled(value) {
538
+ this.toggleAttribute("disabled", Boolean(value));
539
+ }
540
+ get isOpen() {
541
+ return !this.list.hidden;
542
+ }
543
+ open() {
544
+ if (this.disabled || this.isOpen) return;
545
+ this.list.hidden = false;
546
+ this.trigger.setAttribute("aria-expanded", "true");
547
+ this.setActive(this.selectedIndex >= 0 ? this.selectedIndex : this.step(-1, 1));
548
+ }
549
+ close() {
550
+ this.list.hidden = true;
551
+ this.trigger.setAttribute("aria-expanded", "false");
552
+ this.trigger.removeAttribute("aria-activedescendant");
553
+ this.active = -1;
554
+ }
555
+ step(from, dir) {
556
+ for (let i = from + dir; i >= 0 && i < this.opts.length; i += dir) {
557
+ if (!this.opts[i]?.disabled) return i;
558
+ }
559
+ return from;
560
+ }
561
+ setActive(index) {
562
+ this.active = index;
563
+ this.list.querySelectorAll("li").forEach((item, i) => item.toggleAttribute("data-active", i === index));
564
+ if (index >= 0) this.trigger.setAttribute("aria-activedescendant", `${this.domId}-${index}`);
565
+ }
566
+ choose(index) {
567
+ const option = this.opts[index];
568
+ if (!option || option.disabled) return;
569
+ const changed = option.value !== this.current;
570
+ this.value = option.value;
571
+ this.close();
572
+ if (changed) {
573
+ this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
574
+ this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
575
+ }
576
+ }
577
+ onKey(event) {
578
+ if (this.disabled) return;
579
+ const key = event.key;
580
+ if (key === "ArrowDown" || key === "ArrowUp") {
581
+ event.preventDefault();
582
+ if (!this.isOpen) return this.open();
583
+ this.setActive(this.step(this.active, key === "ArrowDown" ? 1 : -1));
584
+ } else if (key === "Home" || key === "End") {
585
+ event.preventDefault();
586
+ if (!this.isOpen) this.open();
587
+ this.setActive(key === "Home" ? this.step(-1, 1) : this.step(this.opts.length, -1));
588
+ } else if (key === "Enter" || key === " ") {
589
+ event.preventDefault();
590
+ if (this.isOpen) this.choose(this.active);
591
+ else this.open();
592
+ } else if (key === "Escape") {
593
+ if (this.isOpen) {
594
+ event.preventDefault();
595
+ event.stopPropagation();
596
+ this.close();
597
+ }
598
+ } else if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
599
+ const now = Date.now();
600
+ this.typed = now - this.typedAt > 700 ? key : this.typed + key;
601
+ this.typedAt = now;
602
+ const needle = this.typed.toLowerCase();
603
+ const index = this.opts.findIndex(
604
+ (option) => !option.disabled && option.label.toLowerCase().startsWith(needle)
605
+ );
606
+ if (index >= 0) {
607
+ if (this.isOpen) this.setActive(index);
608
+ else this.choose(index);
609
+ }
610
+ }
611
+ }
612
+ sync() {
613
+ this.trigger.disabled = this.disabled;
614
+ this.setAttribute("aria-disabled", String(this.disabled));
615
+ const name = this.getAttribute("aria-label");
616
+ if (name) this.trigger.setAttribute("aria-label", name);
617
+ else this.trigger.removeAttribute("aria-label");
618
+ this.label.textContent = this.opts[this.selectedIndex]?.label ?? "";
619
+ this.list.replaceChildren(
620
+ ...this.opts.map((option, index) => {
621
+ const item = this.ownerDocument.createElement("li");
622
+ item.id = `${this.domId}-${index}`;
623
+ item.dataset.index = String(index);
624
+ item.setAttribute("role", "option");
625
+ item.setAttribute("aria-selected", String(option.value === this.current));
626
+ if (option.disabled) item.setAttribute("aria-disabled", "true");
627
+ item.textContent = option.label;
628
+ return item;
629
+ })
630
+ );
631
+ if (this.isOpen) this.setActive(Math.min(this.active, this.opts.length - 1));
632
+ }
633
+ }
634
+ return OfficeUiSelect;
635
+ });
636
+
637
+ // src/status-bar.ts
638
+ var BAR_CSS = `
639
+ :host { display: flex; align-items: center; gap: 12px; min-height: 24px; padding: 0 8px; font-size: 11px;
640
+ background: var(--office-surface, #f3f4f6); color: var(--office-foreground, #1f2937);
641
+ border-top: 1px solid var(--office-border, #d1d5db); font-family: var(--office-font, system-ui, sans-serif); }
642
+ ::slotted([slot="end"]) { margin-inline-start: auto; }
643
+ @media (forced-colors: active) { :host { background: Canvas; color: CanvasText; border-top-color: CanvasText; } }
644
+ `;
645
+ var defineStatusBar = definer("office-ui-status-bar", () => {
646
+ class OfficeUiStatusBar extends HTMLElement {
647
+ constructor() {
648
+ super();
649
+ const root = this.attachShadow({ mode: "open" });
650
+ attachStyles(root, controlCss(BAR_CSS));
651
+ root.append(this.ownerDocument.createElement("slot"));
652
+ }
653
+ connectedCallback() {
654
+ this.setAttribute("role", "group");
655
+ if (!this.hasAttribute("aria-label"))
656
+ this.setAttribute("aria-label", this.getAttribute("label") ?? "Status");
657
+ }
658
+ }
659
+ return OfficeUiStatusBar;
660
+ });
661
+ var ITEM_CSS = `
662
+ :host { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
663
+ .label { color: var(--office-muted-foreground, #6b7280); }
664
+ button { all: unset; display: inline-flex; gap: 4px; align-items: center; cursor: pointer; padding: 0 4px;
665
+ min-height: var(--office-target-size, 24px); border-radius: 3px; }
666
+ button:hover { background: var(--office-background, #fff); }
667
+ button:focus-visible { outline: 2px solid var(--office-ring, #2563eb); }
668
+ @media (forced-colors: active) { .label { color: GrayText; } button:focus-visible { outline-color: Highlight; } }
669
+ `;
670
+ var defineStatusItem = definer("office-ui-status-item", () => {
671
+ class OfficeUiStatusItem extends HTMLElement {
672
+ static observedAttributes = ["label", "value", "interactive"];
673
+ root;
674
+ constructor() {
675
+ super();
676
+ this.root = this.attachShadow({ mode: "open", delegatesFocus: true });
677
+ attachStyles(this.root, controlCss(ITEM_CSS));
678
+ }
679
+ connectedCallback() {
680
+ this.render();
681
+ }
682
+ attributeChangedCallback() {
683
+ this.render();
684
+ }
685
+ get value() {
686
+ return this.getAttribute("value") ?? "";
687
+ }
688
+ set value(next) {
689
+ this.setAttribute("value", String(next));
690
+ }
691
+ render() {
692
+ const doc = this.ownerDocument;
693
+ const label = this.getAttribute("label") ?? "";
694
+ const content = doc.createDocumentFragment();
695
+ if (label) {
696
+ const l = doc.createElement("span");
697
+ l.className = "label";
698
+ l.textContent = label;
699
+ content.append(l);
700
+ }
701
+ const v = doc.createElement("span");
702
+ v.className = "value";
703
+ v.textContent = this.value;
704
+ content.append(v);
705
+ this.root.querySelectorAll(".wrap").forEach((el) => el.remove());
706
+ let wrap;
707
+ if (this.hasAttribute("interactive")) {
708
+ const button = doc.createElement("button");
709
+ button.type = "button";
710
+ button.addEventListener(
711
+ "click",
712
+ () => emit(this, "office-status-activate", { id: this.getAttribute("id") ?? "" })
713
+ );
714
+ wrap = button;
715
+ } else {
716
+ wrap = doc.createElement("span");
717
+ wrap.style.display = "inline-flex";
718
+ wrap.style.gap = "4px";
719
+ }
720
+ wrap.className = "wrap";
721
+ wrap.append(content);
722
+ this.root.append(wrap);
723
+ }
724
+ }
725
+ return OfficeUiStatusItem;
726
+ });
727
+
728
+ // src/controls.ts
729
+ var CONTROL_DEFINERS = [
730
+ defineIcon,
731
+ defineButton,
732
+ defineCheckbox,
733
+ defineSwitch,
734
+ defineSelect,
735
+ defineRibbonGroup,
736
+ defineToolbar,
737
+ defineDialog,
738
+ defineStatusBar,
739
+ defineStatusItem
740
+ ];
741
+ function registerControls(registry) {
742
+ for (const define of CONTROL_DEFINERS) define(registry);
743
+ }
744
+
745
+ export { CONTROL_DEFINERS, defineButton, defineCheckbox, defineDialog, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineToolbar, registerControls };