ooxml-ui 0.14.0 → 0.16.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.
@@ -1,7 +1,7 @@
1
- import { createIconSvg, paintIcon, defineIcon } from './chunk-WNSUNQIY.js';
2
- import { initialsOf } from './chunk-WKLVWPI7.js';
3
- import { definer, attachStyles, controlCss, emit } from './chunk-3BHP4IFP.js';
4
- import { tok, FOCUS_RING, OFFICE_TOKENS } from './chunk-BDJW2KDI.js';
1
+ import { createIconSvg, paintIcon, defineIcon } from './chunk-VUXNXH4Q.js';
2
+ import { initialsOf } from './chunk-VYF7LGXZ.js';
3
+ import { definer, attachStyles, controlCss, emit } from './chunk-NDEB6EBU.js';
4
+ import { tok, FOCUS_RING, OFFICE_TOKENS } from './chunk-4MT4OIAJ.js';
5
5
 
6
6
  // src/chrome-controls.ts
7
7
  var ICON = `svg { flex: none; width: ${tok("--office-icon-size")}; height: ${tok("--office-icon-size")}; fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")};
@@ -702,6 +702,8 @@ var defineRibbonToggle = definer("office-ui-ribbon-toggle", () => {
702
702
  static requestEvent = "office-toggle";
703
703
  static idAttribute = "command";
704
704
  static detailKey = "command";
705
+ /** The checkbox drawn inside; a product subclass may use its own aliased tag. */
706
+ static checkboxTag = "office-ui-checkbox";
705
707
  static get observedAttributes() {
706
708
  return ["label", "checked", "disabled", "title"];
707
709
  }
@@ -713,7 +715,9 @@ var defineRibbonToggle = definer("office-ui-ribbon-toggle", () => {
713
715
  const root = this.attachShadow({ mode: "open" });
714
716
  attachStyles(root, controlCss(CSS2));
715
717
  const label = doc.createElement("label");
716
- this.checkbox = doc.createElement("office-ui-checkbox");
718
+ this.checkbox = doc.createElement(
719
+ this.constructor.checkboxTag
720
+ );
717
721
  this.text = doc.createTextNode("");
718
722
  label.append(this.checkbox, this.text);
719
723
  label.addEventListener("click", (event) => {
@@ -1770,56 +1774,114 @@ var defineAccount = definer("office-ui-account", () => {
1770
1774
  // src/button.ts
1771
1775
  var CSS10 = `
1772
1776
  :host { display: inline-flex; vertical-align: middle; }
1773
- button { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
1777
+ button { box-sizing: border-box; position: relative; display: inline-flex; align-items: center; justify-content: center;
1774
1778
  gap: ${tok("--office-space-1-5")}; min-width: ${tok("--office-target-size")}; min-height: ${tok("--office-target-size")};
1775
1779
  padding: 0 ${tok("--office-space-2")}; border: ${tok("--office-border-width")} solid transparent; border-radius: ${tok("--office-radius")};
1776
1780
  background: transparent; color: ${tok("--office-foreground")}; font: inherit; font-size: ${tok("--office-font-size-sm")};
1777
1781
  cursor: pointer; white-space: nowrap; }
1778
1782
  :host([variant="stacked"]) button { flex-direction: column; padding: ${tok("--office-space-1")} ${tok("--office-space-2")}; }
1779
1783
  button:hover:not(:disabled) { background: ${tok("--office-surface")}; }
1784
+ button:active:not(:disabled) { background: ${tok("--office-selected")}; }
1780
1785
  button:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; outline-offset: calc(${tok("--office-focus-offset")} / 2); }
1781
1786
  button[aria-pressed="true"], button[aria-expanded="true"] { border-color: ${tok("--office-accent")};
1782
1787
  background: ${tok("--office-surface")}; }
1788
+ :host([active]) button { background: ${tok("--office-command-active")}; color: ${tok("--office-accent")}; }
1783
1789
  button:disabled { opacity: .5; cursor: not-allowed; }
1784
- svg { width: ${tok("--office-icon-size-ml")}; height: ${tok("--office-icon-size-ml")}; fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")};
1785
- stroke-linecap: round; stroke-linejoin: round; }
1786
- svg:not([data-painted]) { display: none; }
1790
+ svg { width: ${tok("--office-icon-size-ml")}; height: ${tok("--office-icon-size-ml")}; flex: none; color: ${tok("--office-command-icon-color")};
1791
+ fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; }
1792
+ svg.glyph:not([data-painted]) { display: none; }
1793
+ .label[hidden], .caret[hidden], .badge[hidden] { display: none; }
1794
+ /* Office trails a menu chevron after the last line of the label. */
1795
+ .caret { display: inline-block; width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")};
1796
+ margin-inline-start: ${tok("--office-space-0")}; vertical-align: middle; color: ${tok("--office-muted-foreground")}; }
1797
+ .badge { position: absolute; top: 0; inset-inline-end: 0; padding: 0 ${tok("--office-space-0")}; border-radius: ${tok("--office-radius-full")};
1798
+ background: ${tok("--office-accent")}; color: ${tok("--office-accent-foreground")}; font-size: ${tok("--office-font-size-3xs")}; }
1799
+ /* Large ribbon command: a big glyph above a two-line label. */
1800
+ :host([size="large"]) button { flex-direction: column; justify-content: flex-start; gap: ${tok("--office-space-0")};
1801
+ min-width: ${tok("--office-command-large-min-width")}; max-width: ${tok("--office-command-large-max-width")};
1802
+ height: ${tok("--office-command-large-height")}; padding: ${tok("--office-space-0")} ${tok("--office-space-1")};
1803
+ border: 0; white-space: normal; text-align: center; line-height: ${tok("--office-line-height-tight")}; }
1804
+ :host([size="large"]) svg.glyph { width: ${tok("--office-command-large-icon")}; height: ${tok("--office-command-large-icon")}; }
1805
+ /* Small ribbon command: icon and label in one row. */
1806
+ :host([size="small"]) button { justify-content: flex-start; min-width: 0; height: ${tok("--office-control-height")};
1807
+ min-height: ${tok("--office-control-height")}; padding: 0 ${tok("--office-space-1-5")}; border: 0; text-align: start; }
1808
+ :host([size="small"]) svg.glyph { width: ${tok("--office-command-small-icon")}; height: ${tok("--office-command-small-icon")}; }
1809
+ /* Tall, label-less tool tiles (pens, erasers). */
1810
+ :host([icon-only][tall]) button { min-width: ${tok("--office-command-tall-min-width")}; height: ${tok("--office-command-tall-height")}; padding: ${tok("--office-space-1")}; }
1811
+ :host([icon-only][tall]) svg.glyph { width: ${tok("--office-command-tall-icon")}; height: ${tok("--office-command-tall-icon")}; }
1812
+ :host([icon-only][tall][active]) button { box-shadow: inset 0 0 0 ${tok("--office-border-width")} ${tok("--office-accent")}; }
1813
+ @media (pointer: coarse), (max-width: 767px) {
1814
+ :host([size="small"]) button { min-height: ${tok("--office-target-size-touch")}; }
1815
+ :host([icon-only]) button { min-width: ${tok("--office-target-size-touch")}; }
1816
+ }
1787
1817
  @media (forced-colors: active) {
1788
1818
  button { border-color: ButtonText; color: ButtonText; background: ButtonFace; }
1819
+ svg { color: ButtonText; }
1789
1820
  button:disabled { color: GrayText; border-color: GrayText; }
1790
- button[aria-pressed="true"], button[aria-expanded="true"] { background: Highlight; color: HighlightText; }
1821
+ button[aria-pressed="true"], button[aria-expanded="true"], :host([active]) button { background: Highlight; color: HighlightText; }
1791
1822
  }
1792
1823
  `;
1793
1824
  var defineButton = definer("office-ui-button", () => {
1794
1825
  class OfficeUiButton extends HTMLElement {
1795
- static observedAttributes = [
1796
- "label",
1797
- "icon",
1798
- "command",
1799
- "disabled",
1800
- "pressed",
1801
- "expanded",
1802
- "icon-only",
1803
- "title",
1804
- "keyshortcuts"
1805
- ];
1826
+ static requestEvent = "office-command";
1827
+ static idAttribute = "command";
1828
+ static detailKey = "command";
1829
+ static get observedAttributes() {
1830
+ return [
1831
+ "label",
1832
+ "icon",
1833
+ "command",
1834
+ "disabled",
1835
+ "pressed",
1836
+ "expanded",
1837
+ "icon-only",
1838
+ "title",
1839
+ "keyshortcuts",
1840
+ "badge",
1841
+ "caret",
1842
+ this.idAttribute
1843
+ ];
1844
+ }
1806
1845
  button;
1807
1846
  svg;
1808
1847
  text;
1848
+ caret;
1849
+ badge;
1809
1850
  constructor() {
1810
1851
  super();
1852
+ const doc = this.ownerDocument;
1811
1853
  const root = this.attachShadow({ mode: "open", delegatesFocus: true });
1812
1854
  attachStyles(root, controlCss(CSS10));
1813
- this.button = this.ownerDocument.createElement("button");
1855
+ this.button = doc.createElement("button");
1814
1856
  this.button.type = "button";
1815
1857
  this.button.setAttribute("part", "button");
1816
- this.svg = createIconSvg(this.ownerDocument);
1817
- this.text = this.ownerDocument.createElement("span");
1818
- this.button.append(this.svg, this.text);
1858
+ this.svg = createIconSvg(doc);
1859
+ this.svg.classList.add("glyph");
1860
+ this.text = doc.createElement("span");
1861
+ this.text.className = "label";
1862
+ this.caret = createIconSvg(doc);
1863
+ this.caret.classList.add("caret");
1864
+ paintIcon(this.caret, "chevronDown");
1865
+ this.badge = doc.createElement("span");
1866
+ this.badge.className = "badge";
1867
+ this.badge.setAttribute("aria-hidden", "true");
1868
+ this.button.append(this.svg, this.text, this.badge);
1819
1869
  root.append(this.button);
1870
+ this.button.addEventListener("keydown", (event) => {
1871
+ if ((event.key === " " || event.key === "Enter") && !event.ctrlKey && !event.metaKey && !event.altKey)
1872
+ event.stopPropagation();
1873
+ });
1820
1874
  this.button.addEventListener("click", () => {
1821
- const command = this.getAttribute("command");
1822
- if (!this.button.disabled && command) emit(this, "office-command", { command });
1875
+ const { requestEvent, idAttribute, detailKey } = this.constructor;
1876
+ const id = this.getAttribute(idAttribute);
1877
+ if (!this.button.disabled && id)
1878
+ this.dispatchEvent(
1879
+ new CustomEvent(requestEvent, {
1880
+ detail: { [detailKey]: id },
1881
+ bubbles: true,
1882
+ composed: true
1883
+ })
1884
+ );
1823
1885
  });
1824
1886
  }
1825
1887
  connectedCallback() {
@@ -1842,10 +1904,21 @@ var defineButton = definer("office-ui-button", () => {
1842
1904
  if (value === void 0) this.removeAttribute("pressed");
1843
1905
  else this.setAttribute("pressed", String(value));
1844
1906
  }
1907
+ /** The registered icon to draw; a product subclass may map names onto its glyph set. */
1908
+ iconName() {
1909
+ return this.getAttribute("icon");
1910
+ }
1911
+ /** Whether the trailing menu chevron shows. */
1912
+ showsCaret() {
1913
+ const caret = this.getAttribute("caret");
1914
+ return caret !== null && caret !== "false";
1915
+ }
1845
1916
  sync() {
1846
1917
  const label = this.getAttribute("label") ?? "";
1847
1918
  const iconOnly = this.hasAttribute("icon-only");
1848
1919
  this.text.textContent = label;
1920
+ this.text.append(this.caret);
1921
+ this.caret.toggleAttribute("hidden", !this.showsCaret());
1849
1922
  this.text.hidden = iconOnly;
1850
1923
  if (iconOnly && label) this.button.setAttribute("aria-label", label);
1851
1924
  else this.button.removeAttribute("aria-label");
@@ -1854,8 +1927,10 @@ var defineButton = definer("office-ui-button", () => {
1854
1927
  const shortcuts = this.getAttribute("keyshortcuts");
1855
1928
  if (shortcuts) this.button.setAttribute("aria-keyshortcuts", shortcuts);
1856
1929
  else this.button.removeAttribute("aria-keyshortcuts");
1857
- if (paintIcon(this.svg, this.getAttribute("icon"))) this.svg.setAttribute("data-painted", "");
1930
+ if (paintIcon(this.svg, this.iconName())) this.svg.setAttribute("data-painted", "");
1858
1931
  else this.svg.removeAttribute("data-painted");
1932
+ this.badge.textContent = this.getAttribute("badge") ?? "";
1933
+ this.badge.hidden = !this.badge.textContent;
1859
1934
  for (const attr of ["pressed", "expanded"]) {
1860
1935
  const value = this.getAttribute(attr);
1861
1936
  if (value === null) this.button.removeAttribute(`aria-${attr}`);
@@ -2327,21 +2402,85 @@ var defineCommandSearch = definer("office-ui-command-search", () => {
2327
2402
  return OfficeUiCommandSearch;
2328
2403
  });
2329
2404
 
2405
+ // src/menu-model.ts
2406
+ var EMPTY_MENU_STATE = { x: 0, y: 0, label: "", items: [] };
2407
+ function nextEnabledIndex(items, from, step) {
2408
+ const count = items.length;
2409
+ for (let offset = 1; offset <= count; offset += 1) {
2410
+ const index = ((from + step * offset) % count + count) % count;
2411
+ if (!items[index]?.disabled) return index;
2412
+ }
2413
+ return -1;
2414
+ }
2415
+ function typeAheadIndex(items, from, query) {
2416
+ const needle = query.toLowerCase();
2417
+ const cycling = [...needle].every((char) => char === needle[0]);
2418
+ const prefix = cycling ? needle[0] : needle;
2419
+ for (let offset = cycling ? 1 : 0; offset <= items.length; offset += 1) {
2420
+ const index = (from + offset + items.length) % items.length;
2421
+ const item = items[index];
2422
+ if (item && !item.disabled && item.label.trim().toLowerCase().startsWith(prefix ?? ""))
2423
+ return index;
2424
+ }
2425
+ return -1;
2426
+ }
2427
+ function clampFlyoutPosition(input) {
2428
+ const margin = input.margin ?? 8;
2429
+ const maxLeft = input.viewportWidth - input.width - margin;
2430
+ const maxTop = input.viewportHeight - input.height - margin;
2431
+ return {
2432
+ left: Math.max(margin, input.width > 0 ? Math.min(input.x, maxLeft) : input.x),
2433
+ top: Math.max(margin, input.height > 0 ? Math.min(input.y, maxTop) : input.y)
2434
+ };
2435
+ }
2436
+
2330
2437
  // src/context-menu.ts
2331
2438
  var CSS13 = `
2332
2439
  :host { display: contents; }
2440
+ :host([data-controlled]) { display: block; position: fixed; z-index: ${tok("--office-z-popover")};
2441
+ color: ${tok("--office-popover-foreground")}; font: ${tok("--office-font-size-sm")}/${tok("--office-line-height")} ${tok("--office-font")}; }
2442
+ :host([data-controlled][hidden]) { display: none; }
2333
2443
  .panel { position: fixed; inset: auto; margin: 0; min-width: ${tok("--office-context-menu-min-width")}; padding: ${tok("--office-space-1")}; box-sizing: border-box;
2334
2444
  background: ${tok("--office-background")}; color: ${tok("--office-foreground")};
2335
2445
  border: ${tok("--office-border-width")} solid ${tok("--office-border")}; border-radius: ${tok("--office-radius")};
2336
2446
  box-shadow: ${tok("--office-shadow")}; font-family: ${tok("--office-font")}; }
2337
2447
  .panel[hidden] { display: none; }
2338
- @media (forced-colors: active) { .panel { border-color: CanvasText; background: Canvas; color: CanvasText; } }
2448
+ /* Controlled mode: the host is the fixed box; rows are drawn here from \`state\`. */
2449
+ .menu { box-sizing: border-box; display: flex; flex-direction: column; min-width: ${tok("--office-menu-min-width")};
2450
+ max-height: calc(100vh - 2 * ${tok("--office-space-2")}); overflow-y: auto; padding: ${tok("--office-space-1-5")} 0;
2451
+ border: ${tok("--office-border-width")} solid ${tok("--office-border")}; border-radius: ${tok("--office-radius-md")};
2452
+ background: ${tok("--office-popover")}; color: inherit; box-shadow: ${tok("--office-shadow-lg")}; user-select: none; }
2453
+ .menu:focus { outline: none; }
2454
+ .item { box-sizing: border-box; display: flex; align-items: center; gap: ${tok("--office-space-1-5")}; width: 100%;
2455
+ min-height: ${tok("--office-field-height")}; padding: ${tok("--office-space-1-5")} ${tok("--office-space-3")}; border: 0;
2456
+ background: transparent; color: inherit; font: inherit; text-align: start; white-space: nowrap; cursor: pointer; }
2457
+ .item:hover:not(:disabled), .item:focus:not(:disabled) { background: ${tok("--office-selected")}; outline: none; }
2458
+ .item:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; outline-offset: calc(-1 * ${tok("--office-focus-width")}); }
2459
+ .item:disabled { opacity: .45; cursor: default; }
2460
+ .item.danger { color: ${tok("--office-danger")}; }
2461
+ .item svg { width: ${tok("--office-icon-size-md")}; height: ${tok("--office-icon-size-md")}; flex: none; fill: none; stroke: currentColor;
2462
+ stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; }
2463
+ .check { flex: none; width: ${tok("--office-icon-size")}; text-align: center; }
2464
+ .separator { height: ${tok("--office-border-width")}; margin: ${tok("--office-space-1")} 0; background: ${tok("--office-border")}; }
2465
+ .heading { padding: ${tok("--office-space-1-5")} ${tok("--office-space-3")} ${tok("--office-space-0")}; color: ${tok("--office-muted-foreground")};
2466
+ font-size: ${tok("--office-font-size-2xs")}; font-weight: ${tok("--office-font-weight-bold")}; letter-spacing: .06em; text-transform: uppercase; }
2467
+ @media (pointer: coarse) { .item { min-height: ${tok("--office-target-size-touch")}; } }
2468
+ @media (forced-colors: active) {
2469
+ .panel, .menu { border-color: CanvasText; background: Canvas; color: CanvasText; box-shadow: none; }
2470
+ .item, .item.danger { color: CanvasText; }
2471
+ .item:disabled { color: GrayText; opacity: 1; }
2472
+ .item:hover:not(:disabled), .item:focus:not(:disabled) { background: Highlight; color: HighlightText; }
2473
+ .separator { background: CanvasText; }
2474
+ .heading { color: CanvasText; }
2475
+ }
2339
2476
  `;
2340
2477
  var SEPARATOR_CSS = `
2341
2478
  :host { display: block; height: ${tok("--office-border-width")}; margin: ${tok("--office-space-1")} ${tok("--office-space-1-5")}; background: ${tok("--office-border")}; }
2342
2479
  @media (forced-colors: active) { :host { background: CanvasText; } }
2343
2480
  `;
2344
2481
  var ITEMS = "office-ui-menu-item:not([disabled]):not([hidden])";
2482
+ var TYPE_AHEAD_MS = 700;
2483
+ var openMenus = /* @__PURE__ */ new WeakMap();
2345
2484
  var defineMenuSeparator = definer("office-ui-menu-separator", () => {
2346
2485
  class OfficeUiMenuSeparator extends HTMLElement {
2347
2486
  constructor() {
@@ -2356,10 +2495,25 @@ var defineMenuSeparator = definer("office-ui-menu-separator", () => {
2356
2495
  });
2357
2496
  var defineContextMenu = definer("office-ui-context-menu", () => {
2358
2497
  class OfficeUiContextMenu extends HTMLElement {
2498
+ static requestEvent = "office-menu-request";
2499
+ static closeEvent = "office-menu-close";
2500
+ static markerPattern = /^data-[a-z0-9-]+$/;
2359
2501
  static observedAttributes = ["label"];
2360
2502
  panel;
2503
+ menu;
2361
2504
  returnFocus = null;
2362
2505
  outside;
2506
+ model = EMPTY_MENU_STATE;
2507
+ controlled = false;
2508
+ rows = [];
2509
+ active = -1;
2510
+ focused = false;
2511
+ query = "";
2512
+ queryTimer;
2513
+ markers = [];
2514
+ listening = false;
2515
+ signature = "";
2516
+ opener = null;
2363
2517
  constructor() {
2364
2518
  super();
2365
2519
  const doc = this.ownerDocument;
@@ -2372,22 +2526,49 @@ var defineContextMenu = definer("office-ui-context-menu", () => {
2372
2526
  this.panel.setAttribute("popover", "auto");
2373
2527
  else this.panel.hidden = true;
2374
2528
  this.panel.append(doc.createElement("slot"));
2375
- root.append(this.panel);
2529
+ this.menu = doc.createElement("div");
2530
+ this.menu.className = "menu";
2531
+ this.menu.setAttribute("role", "menu");
2532
+ this.menu.setAttribute("aria-orientation", "vertical");
2533
+ this.menu.tabIndex = -1;
2534
+ this.menu.hidden = true;
2535
+ root.append(this.panel, this.menu);
2376
2536
  this.panel.addEventListener("toggle", (event) => {
2377
2537
  if (event.newState === "closed") this.closed(false);
2378
2538
  });
2379
- this.panel.addEventListener("keydown", (event) => this.onKey(event));
2539
+ this.panel.addEventListener("keydown", (event) => this.onChildKey(event));
2540
+ this.menu.addEventListener("keydown", (event) => this.onRowKey(event));
2541
+ this.menu.addEventListener("pointerover", (event) => {
2542
+ const row = event.target?.closest("button");
2543
+ const index = row ? this.rows.indexOf(row) : -1;
2544
+ if (index >= 0 && !row?.disabled && index !== this.active) this.focusRow(index);
2545
+ });
2546
+ this.menu.addEventListener("contextmenu", (event) => event.preventDefault());
2380
2547
  this.addEventListener("office-command", () => this.close());
2381
2548
  }
2382
2549
  connectedCallback() {
2383
2550
  this.panel.setAttribute("aria-label", this.getAttribute("label") ?? "Context menu");
2551
+ if (this.controlled) {
2552
+ this.opener = this.deepActive();
2553
+ this.render();
2554
+ }
2384
2555
  }
2385
2556
  attributeChangedCallback() {
2386
2557
  this.panel.setAttribute("aria-label", this.getAttribute("label") ?? "Context menu");
2387
2558
  }
2388
2559
  disconnectedCallback() {
2389
2560
  this.close();
2561
+ if (!this.controlled) return;
2562
+ this.stopListening();
2563
+ clearTimeout(this.queryTimer);
2564
+ this.focused = false;
2565
+ const opener = this.opener;
2566
+ this.opener = null;
2567
+ const active = this.ownerDocument.activeElement;
2568
+ if (opener?.isConnected && (!active || active === this.ownerDocument.body))
2569
+ opener.focus({ preventScroll: true });
2390
2570
  }
2571
+ // ---- Children mode ------------------------------------------------------------------
2391
2572
  get open() {
2392
2573
  return this.hasAttribute("data-open");
2393
2574
  }
@@ -2410,12 +2591,17 @@ var defineContextMenu = definer("office-ui-context-menu", () => {
2410
2591
  }
2411
2592
  this.setAttribute("data-open", "");
2412
2593
  const view = doc.defaultView;
2413
- const width = this.panel.offsetWidth;
2414
- const height = this.panel.offsetHeight;
2415
- const left = view && width ? Math.min(x, view.innerWidth - width - 4) : x;
2416
- const top = view && height ? Math.min(y, view.innerHeight - height - 4) : y;
2417
- this.panel.style.left = `${Math.max(4, Math.round(left))}px`;
2418
- this.panel.style.top = `${Math.max(4, Math.round(top))}px`;
2594
+ const { left, top } = clampFlyoutPosition({
2595
+ x,
2596
+ y,
2597
+ width: this.panel.offsetWidth,
2598
+ height: this.panel.offsetHeight,
2599
+ viewportWidth: view?.innerWidth ?? x,
2600
+ viewportHeight: view?.innerHeight ?? y,
2601
+ margin: 4
2602
+ });
2603
+ this.panel.style.left = `${Math.round(left)}px`;
2604
+ this.panel.style.top = `${Math.round(top)}px`;
2419
2605
  this.items()[0]?.focus();
2420
2606
  }
2421
2607
  close() {
@@ -2436,7 +2622,7 @@ var defineContextMenu = definer("office-ui-context-menu", () => {
2436
2622
  if (restoreFocus) this.returnFocus?.focus?.();
2437
2623
  this.returnFocus = null;
2438
2624
  }
2439
- onKey(event) {
2625
+ onChildKey(event) {
2440
2626
  const items = this.items();
2441
2627
  const from = items.findIndex(
2442
2628
  (item) => item === event.target || item.contains(event.target)
@@ -2452,8 +2638,217 @@ var defineContextMenu = definer("office-ui-context-menu", () => {
2452
2638
  else if (event.key === "Escape" || event.key === "Tab") {
2453
2639
  if (event.key === "Escape") event.preventDefault();
2454
2640
  this.close();
2641
+ } else if (event.key.length === 1 && event.key !== " " && !event.ctrlKey && !event.metaKey && !event.altKey) {
2642
+ const labels = items.map((item) => ({
2643
+ label: item.getAttribute("label") ?? item.textContent ?? ""
2644
+ }));
2645
+ const target = this.typeAhead(labels, from, event.key);
2646
+ if (target >= 0) move(target);
2647
+ }
2648
+ }
2649
+ // ---- Controlled mode ----------------------------------------------------------------
2650
+ get state() {
2651
+ return this.model;
2652
+ }
2653
+ set state(value) {
2654
+ const signature = JSON.stringify(value);
2655
+ this.model = value;
2656
+ if (!this.controlled) {
2657
+ this.controlled = true;
2658
+ this.toggleAttribute("data-controlled", true);
2659
+ this.panel.remove();
2660
+ if (this.isConnected) this.opener = this.deepActive();
2661
+ }
2662
+ if (signature !== this.signature) {
2663
+ this.signature = signature;
2664
+ this.render();
2455
2665
  }
2456
2666
  }
2667
+ config() {
2668
+ return this.constructor;
2669
+ }
2670
+ deepActive() {
2671
+ let node = this.ownerDocument.activeElement;
2672
+ while (node?.shadowRoot?.activeElement) node = node.shadowRoot.activeElement;
2673
+ return node instanceof HTMLElement && node !== this.ownerDocument.body ? node : null;
2674
+ }
2675
+ render() {
2676
+ const state = this.model;
2677
+ const items = state.items;
2678
+ this.hidden = items.length === 0;
2679
+ this.menu.hidden = items.length === 0;
2680
+ this.applyMarkers(state.markers ?? []);
2681
+ if (state.zIndex !== void 0) this.style.zIndex = String(state.zIndex);
2682
+ else this.style.removeProperty("z-index");
2683
+ const same = items[this.active];
2684
+ this.active = same && !same.disabled ? this.active : nextEnabledIndex(items, -1, 1);
2685
+ this.renderRows();
2686
+ if (items.length === 0) {
2687
+ this.stopListening();
2688
+ this.focused = false;
2689
+ }
2690
+ if (!this.isConnected || items.length === 0) return;
2691
+ this.place();
2692
+ this.listen();
2693
+ if (!this.focused && state.autoFocus !== false) {
2694
+ this.focused = true;
2695
+ this.focusRow(nextEnabledIndex(items, -1, 1));
2696
+ } else if (this.focused && this.active >= 0)
2697
+ this.rows[this.active]?.focus({ preventScroll: true });
2698
+ }
2699
+ renderRows() {
2700
+ const doc = this.ownerDocument;
2701
+ const state = this.model;
2702
+ this.menu.setAttribute("aria-label", state.label);
2703
+ this.rows = [];
2704
+ let container = this.menu;
2705
+ const nodes = [];
2706
+ const place = (node) => container === this.menu ? nodes.push(node) : container.append(node);
2707
+ state.items.forEach((item, index) => {
2708
+ if (item.separatorBefore || item.heading) container = this.menu;
2709
+ if (item.separatorBefore) {
2710
+ const rule = doc.createElement("div");
2711
+ rule.className = "separator";
2712
+ rule.setAttribute("role", "separator");
2713
+ place(rule);
2714
+ }
2715
+ if (item.heading) {
2716
+ const group = doc.createElement("div");
2717
+ group.setAttribute("role", "group");
2718
+ group.setAttribute("aria-label", item.heading);
2719
+ const title = doc.createElement("div");
2720
+ title.className = "heading";
2721
+ title.setAttribute("aria-hidden", "true");
2722
+ title.textContent = item.heading;
2723
+ group.append(title);
2724
+ place(group);
2725
+ container = group;
2726
+ }
2727
+ const row = doc.createElement("button");
2728
+ row.type = "button";
2729
+ row.className = item.danger ? "item danger" : "item";
2730
+ row.dataset.itemId = item.id;
2731
+ row.setAttribute("role", item.checked === void 0 ? "menuitem" : "menuitemcheckbox");
2732
+ if (item.checked !== void 0) {
2733
+ row.setAttribute("aria-checked", String(item.checked));
2734
+ const check = doc.createElement("span");
2735
+ check.className = "check";
2736
+ check.setAttribute("aria-hidden", "true");
2737
+ check.textContent = item.checked ? "\u2713" : "";
2738
+ row.append(check);
2739
+ }
2740
+ if (item.icon) {
2741
+ const glyph = createIconSvg(doc);
2742
+ paintIcon(glyph, item.icon);
2743
+ row.append(glyph);
2744
+ }
2745
+ row.append(doc.createTextNode(item.label));
2746
+ row.tabIndex = index === this.active ? 0 : -1;
2747
+ if (item.disabled) {
2748
+ row.disabled = true;
2749
+ row.setAttribute("aria-disabled", "true");
2750
+ }
2751
+ row.addEventListener(
2752
+ "click",
2753
+ () => this.dispatchEvent(
2754
+ new CustomEvent(this.config().requestEvent, {
2755
+ detail: { id: item.id },
2756
+ bubbles: true,
2757
+ composed: true
2758
+ })
2759
+ )
2760
+ );
2761
+ this.rows.push(row);
2762
+ place(row);
2763
+ });
2764
+ this.menu.replaceChildren(...nodes);
2765
+ }
2766
+ applyMarkers(next) {
2767
+ for (const name of this.markers) this.removeAttribute(name);
2768
+ const pattern = this.config().markerPattern;
2769
+ this.markers = next.filter((name) => pattern.test(name));
2770
+ for (const name of this.markers) this.setAttribute(name, "true");
2771
+ }
2772
+ place() {
2773
+ const view = this.ownerDocument.defaultView;
2774
+ this.style.left = `${this.model.x}px`;
2775
+ this.style.top = `${this.model.y}px`;
2776
+ const box = this.getBoundingClientRect();
2777
+ const { left, top } = clampFlyoutPosition({
2778
+ x: this.model.x,
2779
+ y: this.model.y,
2780
+ width: box.width,
2781
+ height: box.height,
2782
+ viewportWidth: view?.innerWidth ?? box.right,
2783
+ viewportHeight: view?.innerHeight ?? box.bottom
2784
+ });
2785
+ this.style.left = `${left}px`;
2786
+ this.style.top = `${top}px`;
2787
+ }
2788
+ focusRow(index) {
2789
+ if (index < 0) return;
2790
+ this.active = index;
2791
+ this.rows.forEach((row, at) => row.tabIndex = at === index ? 0 : -1);
2792
+ this.rows[index]?.focus({ preventScroll: true });
2793
+ }
2794
+ typeAhead(items, from, key) {
2795
+ this.query += key;
2796
+ clearTimeout(this.queryTimer);
2797
+ this.queryTimer = setTimeout(() => this.query = "", TYPE_AHEAD_MS);
2798
+ return typeAheadIndex(items, from, this.query);
2799
+ }
2800
+ onRowKey(event) {
2801
+ const items = this.model.items;
2802
+ let target = -2;
2803
+ if (event.key === "ArrowDown") target = nextEnabledIndex(items, this.active, 1);
2804
+ else if (event.key === "ArrowUp") target = nextEnabledIndex(items, this.active, -1);
2805
+ else if (event.key === "Home") target = nextEnabledIndex(items, -1, 1);
2806
+ else if (event.key === "End") target = nextEnabledIndex(items, items.length, -1);
2807
+ else if (event.key.length === 1 && event.key !== " " && !event.ctrlKey && !event.metaKey && !event.altKey)
2808
+ target = this.typeAhead(items, this.active, event.key);
2809
+ if (target === -2) return;
2810
+ event.preventDefault();
2811
+ this.focusRow(target);
2812
+ }
2813
+ dismiss(reason) {
2814
+ this.dispatchEvent(
2815
+ new CustomEvent(this.config().closeEvent, {
2816
+ detail: { reason },
2817
+ bubbles: true,
2818
+ composed: true
2819
+ })
2820
+ );
2821
+ }
2822
+ onDocumentKey = (event) => {
2823
+ if (openMenus.get(this.ownerDocument)?.at(-1) !== this) return;
2824
+ if (event.key === "Escape") {
2825
+ event.preventDefault();
2826
+ event.stopPropagation();
2827
+ this.dismiss("escape");
2828
+ } else if (event.key === "Tab") this.dismiss("tab");
2829
+ };
2830
+ onDocumentPointer = (event) => {
2831
+ if (!event.composedPath().includes(this)) this.dismiss("outside");
2832
+ };
2833
+ listen() {
2834
+ if (this.listening) return;
2835
+ this.listening = true;
2836
+ const doc = this.ownerDocument;
2837
+ openMenus.set(doc, [...openMenus.get(doc) ?? [], this]);
2838
+ doc.addEventListener("keydown", this.onDocumentKey, true);
2839
+ doc.addEventListener("pointerdown", this.onDocumentPointer, true);
2840
+ }
2841
+ stopListening() {
2842
+ if (!this.listening) return;
2843
+ this.listening = false;
2844
+ const doc = this.ownerDocument;
2845
+ openMenus.set(
2846
+ doc,
2847
+ (openMenus.get(doc) ?? []).filter((menu) => menu !== this)
2848
+ );
2849
+ doc.removeEventListener("keydown", this.onDocumentKey, true);
2850
+ doc.removeEventListener("pointerdown", this.onDocumentPointer, true);
2851
+ }
2457
2852
  }
2458
2853
  return OfficeUiContextMenu;
2459
2854
  });
@@ -3037,41 +3432,90 @@ function text(doc, tag, content, className) {
3037
3432
  // src/ribbon.ts
3038
3433
  var GROUP_CSS = `
3039
3434
  :host { display: inline-flex; flex: none; align-self: stretch; }
3040
- .group { box-sizing: border-box; display: flex; flex-direction: column; justify-content: space-between;
3041
- min-height: ${tok("--office-ribbon-group-height")}; padding: ${tok("--office-space-1")} ${tok("--office-space-2")} ${tok("--office-space-0")};
3042
- border-inline-end: ${tok("--office-border-width")} solid color-mix(in srgb, ${tok("--office-border")} 60%, transparent); }
3043
- :host(:last-child) .group { border-inline-end: 0; }
3044
- .row { display: flex; align-items: flex-start; gap: ${tok("--office-space-1")}; }
3435
+ .group { position: relative; box-sizing: border-box; display: flex; flex-direction: column; justify-content: space-between;
3436
+ min-height: ${tok("--office-ribbon-group-height")}; padding: ${tok("--office-ribbon-group-padding")}; }
3437
+ /* Office draws a hairline between groups; a product may stop it short of the top and bottom. */
3438
+ .group::after { content: ''; position: absolute; top: ${tok("--office-ribbon-separator-inset")}; bottom: ${tok("--office-ribbon-separator-inset")};
3439
+ inset-inline-end: 0; width: ${tok("--office-border-width")}; background: ${tok("--office-ribbon-separator")}; }
3440
+ :host(:last-child) .group::after { content: none; }
3441
+ .row { display: flex; align-items: flex-start; gap: ${tok("--office-space-1")}; flex: 1; }
3442
+ /* One-line groups centre their commands on the whole group box. */
3443
+ :host([data-compact-row]) .row { align-items: center; }
3444
+ /* Several small drop-down galleries stack in columns of three. */
3445
+ :host([data-stack]) .row { flex-direction: column; align-items: stretch; align-content: flex-start; flex-wrap: wrap;
3446
+ max-height: ${tok("--office-ribbon-stack-max-height")}; gap: ${tok("--office-space-0")}; }
3045
3447
  ::slotted(*) { flex-shrink: 0; }
3046
- .foot { position: relative; display: flex; justify-content: center; padding-top: ${tok("--office-space-0")}; }
3047
- .caption { color: ${tok("--office-muted-foreground")}; font-size: ${tok("--office-font-size-2xs")};
3448
+ .foot { position: relative; display: flex; align-items: center; justify-content: center; padding: ${tok("--office-ribbon-caption-padding")}; }
3449
+ .caption { color: ${tok("--office-muted-foreground")}; font-size: ${tok("--office-ribbon-caption-size")};
3048
3450
  line-height: ${tok("--office-ribbon-caption-line-height")}; text-align: center; white-space: nowrap; }
3049
- .launcher { position: absolute; inset-inline-end: -6px; bottom: calc(-1 * ${tok("--office-space-px")}); display: inline-grid; place-items: center;
3050
- width: ${tok("--office-launcher-width")}; height: ${tok("--office-launcher-height")}; padding: 0; border: 0; border-radius: ${tok("--office-radius-xs")}; background: transparent;
3051
- color: ${tok("--office-muted-foreground")}; cursor: pointer; }
3052
- .launcher:hover:not(:disabled) { background: ${tok("--office-surface")}; }
3451
+ .launcher { position: absolute; inset-inline-end: ${tok("--office-launcher-inset-end")}; bottom: ${tok("--office-launcher-inset-bottom")}; display: inline-grid; place-items: center;
3452
+ width: ${tok("--office-launcher-width")}; height: ${tok("--office-launcher-height")}; padding: 0; border: 0; border-radius: ${tok("--office-radius-xs")};
3453
+ background: transparent; color: ${tok("--office-muted-foreground")}; cursor: pointer; }
3454
+ .launcher:hover:not(:disabled) { background: ${tok("--office-surface")}; color: ${tok("--office-foreground")}; }
3053
3455
  .launcher:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; }
3054
3456
  .launcher:disabled { opacity: .45; cursor: not-allowed; }
3055
3457
  .launcher[hidden] { display: none; }
3056
- .launcher svg { width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")}; fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")};
3057
- stroke-linecap: round; stroke-linejoin: round; }
3058
- @media (forced-colors: active) { .group { border-inline-end-color: CanvasText; } .caption { color: CanvasText; } }
3458
+ .launcher svg { width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")}; fill: none; stroke: currentColor;
3459
+ stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; }
3460
+ /* Collapsed (narrow window): the group is one button; its commands open in a popup. */
3461
+ .face { display: none; }
3462
+ :host([data-collapsed]) .face { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: ${tok("--office-space-0")};
3463
+ min-width: ${tok("--office-collapsed-group-min-width")}; height: ${tok("--office-command-large-height")}; padding: ${tok("--office-space-0")} ${tok("--office-space-1-5")};
3464
+ border: 0; border-radius: ${tok("--office-radius")}; background: transparent; color: ${tok("--office-foreground")}; font: inherit;
3465
+ font-size: ${tok("--office-font-size-sm")}; cursor: pointer; }
3466
+ :host([data-collapsed]) .face:hover, :host([data-open]) .face { background: ${tok("--office-selected")}; }
3467
+ .face:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; outline-offset: ${tok("--office-focus-offset")}; }
3468
+ .face svg { width: ${tok("--office-command-large-icon")}; height: ${tok("--office-command-large-icon")}; fill: none; stroke: currentColor;
3469
+ stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; color: ${tok("--office-command-icon-color")}; }
3470
+ .face svg.chev { width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")}; color: currentColor; }
3471
+ :host([data-collapsed]) .foot, :host([data-collapsed]) .row { display: none; }
3472
+ :host([data-collapsed][data-open]) .row { display: flex; flex-wrap: wrap; position: fixed; top: ${tok("--office-ribbon-collapse-y")};
3473
+ left: ${tok("--office-ribbon-collapse-x")}; z-index: ${tok("--office-z-popover")}; box-sizing: border-box;
3474
+ max-width: calc(100vw - 2 * ${tok("--office-space-2")}); padding: ${tok("--office-space-2")}; background: ${tok("--office-popover")};
3475
+ color: ${tok("--office-popover-foreground")}; border: ${tok("--office-border-width")} solid ${tok("--office-border")};
3476
+ border-radius: ${tok("--office-radius-md")}; box-shadow: ${tok("--office-shadow")}; }
3477
+ @media (forced-colors: active) {
3478
+ .group::after { background: CanvasText; } .caption, .launcher { color: CanvasText; }
3479
+ :host([data-collapsed][data-open]) .row { border-color: CanvasText; }
3480
+ }
3059
3481
  `;
3482
+ function tokenPx(el, name, fallback) {
3483
+ const value = el.ownerDocument.defaultView?.getComputedStyle(el).getPropertyValue(name).trim();
3484
+ const px = value?.endsWith("px") ? parseFloat(value) : NaN;
3485
+ return Number.isFinite(px) ? px : fallback;
3486
+ }
3060
3487
  var defineRibbonGroup = definer("office-ui-ribbon-group", () => {
3061
3488
  class OfficeUiRibbonGroup extends HTMLElement {
3062
- static observedAttributes = ["label", "launcher", "launcher-disabled"];
3489
+ static launcherEvent = "office-command";
3490
+ static collapseEvent = "office-ribbon-collapse-toggle";
3491
+ static observedAttributes = [
3492
+ "label",
3493
+ "launcher",
3494
+ "launcher-label",
3495
+ "launcher-disabled",
3496
+ "icon",
3497
+ "data-open"
3498
+ ];
3063
3499
  caption;
3064
3500
  launcher;
3501
+ face;
3502
+ faceIcon;
3503
+ faceLabel;
3504
+ slotEl;
3505
+ observer;
3065
3506
  constructor() {
3066
3507
  super();
3067
3508
  const doc = this.ownerDocument;
3068
3509
  const root = this.attachShadow({ mode: "open" });
3069
3510
  attachStyles(root, controlCss(GROUP_CSS));
3511
+ const config3 = () => this.constructor;
3070
3512
  const group = doc.createElement("div");
3071
3513
  group.className = "group";
3072
3514
  const row = doc.createElement("div");
3073
3515
  row.className = "row";
3074
- row.append(doc.createElement("slot"));
3516
+ this.slotEl = doc.createElement("slot");
3517
+ this.slotEl.addEventListener("slotchange", () => this.observe());
3518
+ row.append(this.slotEl);
3075
3519
  this.caption = doc.createElement("span");
3076
3520
  this.caption.className = "caption";
3077
3521
  this.launcher = doc.createElement("button");
@@ -3081,30 +3525,94 @@ var defineRibbonGroup = definer("office-ui-ribbon-group", () => {
3081
3525
  paintIcon(glyph, "launcher");
3082
3526
  this.launcher.append(glyph);
3083
3527
  this.launcher.addEventListener("click", () => {
3084
- const command = this.getAttribute("launcher");
3085
- if (command && !this.launcher.disabled) emit(this, "office-command", { command });
3528
+ if (this.launcher.disabled) return;
3529
+ const detail = this.launcherDetail();
3530
+ if (detail)
3531
+ this.dispatchEvent(
3532
+ new CustomEvent(config3().launcherEvent, { detail, bubbles: true, composed: true })
3533
+ );
3086
3534
  });
3087
3535
  const foot = doc.createElement("div");
3088
3536
  foot.className = "foot";
3089
3537
  foot.append(this.caption, this.launcher);
3090
- group.append(row, foot);
3538
+ this.face = doc.createElement("button");
3539
+ this.face.type = "button";
3540
+ this.face.className = "face";
3541
+ this.face.setAttribute("aria-haspopup", "true");
3542
+ this.faceIcon = createIconSvg(doc);
3543
+ this.faceLabel = doc.createElement("span");
3544
+ const chevron = createIconSvg(doc);
3545
+ chevron.classList.add("chev");
3546
+ paintIcon(chevron, "chevronDown");
3547
+ this.face.append(this.faceIcon, this.faceLabel, chevron);
3548
+ this.face.addEventListener(
3549
+ "click",
3550
+ () => this.dispatchEvent(
3551
+ new CustomEvent(config3().collapseEvent, { bubbles: true, composed: true })
3552
+ )
3553
+ );
3554
+ group.append(this.face, row, foot);
3091
3555
  root.append(group);
3092
3556
  }
3093
3557
  connectedCallback() {
3094
3558
  this.setAttribute("role", "group");
3095
3559
  this.sync();
3560
+ this.observe();
3561
+ }
3562
+ disconnectedCallback() {
3563
+ this.observer?.disconnect();
3096
3564
  }
3097
3565
  attributeChangedCallback() {
3098
3566
  this.sync();
3099
3567
  }
3568
+ /** The launcher's event detail; null emits nothing. */
3569
+ launcherDetail() {
3570
+ const command = this.getAttribute("launcher");
3571
+ return command ? { command } : null;
3572
+ }
3573
+ /** The registered icon on the collapsed face; a product subclass may map names. */
3574
+ iconName() {
3575
+ return this.getAttribute("icon") ?? "grid";
3576
+ }
3577
+ observe() {
3578
+ this.observer?.disconnect();
3579
+ if (!this.isConnected) return;
3580
+ const Observer = this.ownerDocument.defaultView?.ResizeObserver;
3581
+ if (Observer) this.observer ??= new Observer(() => this.measure());
3582
+ for (const el of this.slotEl.assignedElements()) this.observer?.observe(el);
3583
+ this.measure();
3584
+ }
3585
+ measure() {
3586
+ const children = [...this.children];
3587
+ const stackable = children.filter((el) => {
3588
+ const gallery = el.matches('[mode="dropdown"]') ? el : el.querySelector('[mode="dropdown"]');
3589
+ return gallery !== null && !gallery.hasAttribute("data-command-large");
3590
+ });
3591
+ this.toggleAttribute(
3592
+ "data-stack",
3593
+ stackable.length > 1 && stackable.length === children.length
3594
+ );
3595
+ const tallest = Math.max(0, ...children.map((el) => el.getBoundingClientRect().height));
3596
+ if (tallest > 0)
3597
+ this.toggleAttribute(
3598
+ "data-compact-row",
3599
+ tallest <= tokenPx(this, "--office-ribbon-compact-row-max", 36)
3600
+ );
3601
+ }
3100
3602
  sync() {
3101
3603
  const label = this.getAttribute("label") ?? "";
3102
3604
  this.caption.textContent = label;
3103
3605
  this.setAttribute("aria-label", label);
3104
- this.launcher.hidden = !this.getAttribute("launcher");
3606
+ const launcherLabel = this.getAttribute("launcher-label") ?? `${label} options`;
3607
+ this.launcher.hidden = !this.hasAttribute("launcher");
3105
3608
  this.launcher.disabled = this.hasAttribute("launcher-disabled");
3106
- this.launcher.setAttribute("aria-label", `${label} options`);
3107
- this.launcher.title = `${label} options`;
3609
+ this.launcher.setAttribute("aria-label", launcherLabel);
3610
+ this.launcher.title = launcherLabel;
3611
+ this.faceLabel.textContent = label;
3612
+ this.face.title = label;
3613
+ this.face.setAttribute("aria-label", label);
3614
+ this.face.setAttribute("aria-expanded", String(this.hasAttribute("data-open")));
3615
+ paintIcon(this.faceIcon, this.iconName());
3108
3616
  }
3109
3617
  }
3110
3618
  return OfficeUiRibbonGroup;
@@ -3227,7 +3735,7 @@ function renderSelectMenu(menu, choices, value) {
3227
3735
  });
3228
3736
  menu.replaceChildren(...items);
3229
3737
  }
3230
- function tokenPx(el, name, fallback) {
3738
+ function tokenPx2(el, name, fallback) {
3231
3739
  const value = el.ownerDocument.defaultView?.getComputedStyle(el).getPropertyValue(name).trim();
3232
3740
  const px = value?.endsWith("px") ? parseFloat(value) : NaN;
3233
3741
  return Number.isFinite(px) ? px : fallback;
@@ -3235,14 +3743,14 @@ function tokenPx(el, name, fallback) {
3235
3743
  function positionSelectMenu(menu, trigger, host) {
3236
3744
  const view = menu.ownerDocument.defaultView;
3237
3745
  const rect = trigger.getBoundingClientRect();
3238
- const margin = tokenPx(host, "--office-space-2", 8);
3239
- const gap = tokenPx(host, "--office-space-1", 4);
3746
+ const margin = tokenPx2(host, "--office-space-2", 8);
3747
+ const gap = tokenPx2(host, "--office-space-1", 4);
3240
3748
  const width = Math.max(0, view.innerWidth - margin * 2);
3241
3749
  const below = Math.max(0, view.innerHeight - rect.bottom - gap - margin);
3242
3750
  const above = Math.max(0, rect.top - gap - margin);
3243
3751
  menu.style.minWidth = `${Math.min(rect.width, width)}px`;
3244
3752
  menu.style.maxWidth = `${width}px`;
3245
- const cap = host.getAttribute("data-font-picker") === "family" ? tokenPx(host, "--office-font-picker-max-height", 320) : tokenPx(host, "--office-select-max-height", 240);
3753
+ const cap = host.getAttribute("data-font-picker") === "family" ? tokenPx2(host, "--office-font-picker-max-height", 320) : tokenPx2(host, "--office-select-max-height", 240);
3246
3754
  menu.style.maxHeight = `${cap}px`;
3247
3755
  const preferred = menu.getBoundingClientRect().height;
3248
3756
  const flip = below < preferred && above > below;
@@ -4258,4 +4766,4 @@ function registerControls(registry) {
4258
4766
  for (const define of CONTROL_DEFINERS) define(registry);
4259
4767
  }
4260
4768
 
4261
- export { CONTROL_DEFINERS, DEFAULT_OFFICE_PROFILE, OFFICE_AVATAR_SWATCHES, OFFICE_PROFILE_STORAGE_KEY, OFFICE_TOAST_VISIBLE_LIMIT, attachKeyTips, clampOptionNumber, clearOfficeProfile, defineAccount, defineBackstage, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineDialogFooter, defineFindBar, defineMenuButton, defineMenuItem, defineMenuSeparator, defineOptionsDialog, definePasteOptions, definePrintPreview, defineRadio, defineReadOnlyBanner, defineRibbon, defineRibbonGroup, defineRibbonStack, defineRibbonToggle, defineRuler, defineSearchField, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToasts, defineToolbar, defineZoomSlider, profileInitials, readOfficeProfile, registerControls, rulerDivisions, sanitizeOfficeProfile, writeOfficeProfile };
4769
+ export { CONTROL_DEFINERS, DEFAULT_OFFICE_PROFILE, EMPTY_MENU_STATE, OFFICE_AVATAR_SWATCHES, OFFICE_PROFILE_STORAGE_KEY, OFFICE_TOAST_VISIBLE_LIMIT, attachKeyTips, clampFlyoutPosition, clampOptionNumber, clearOfficeProfile, defineAccount, defineBackstage, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineDialogFooter, defineFindBar, defineMenuButton, defineMenuItem, defineMenuSeparator, defineOptionsDialog, definePasteOptions, definePrintPreview, defineRadio, defineReadOnlyBanner, defineRibbon, defineRibbonGroup, defineRibbonStack, defineRibbonToggle, defineRuler, defineSearchField, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToasts, defineToolbar, defineZoomSlider, nextEnabledIndex, profileInitials, readOfficeProfile, registerControls, rulerDivisions, sanitizeOfficeProfile, typeAheadIndex, writeOfficeProfile };