ooxml-ui 0.13.0 → 0.15.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-4NUKVNSJ.js';
2
- import { initialsOf } from './chunk-NOQAM6ZS.js';
3
- import { definer, attachStyles, controlCss, emit } from './chunk-D6EREKYF.js';
4
- import { tok, OFFICE_TOKENS } from './chunk-MDW2Y5UN.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")};
@@ -1770,56 +1770,114 @@ var defineAccount = definer("office-ui-account", () => {
1770
1770
  // src/button.ts
1771
1771
  var CSS10 = `
1772
1772
  :host { display: inline-flex; vertical-align: middle; }
1773
- button { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
1773
+ button { box-sizing: border-box; position: relative; display: inline-flex; align-items: center; justify-content: center;
1774
1774
  gap: ${tok("--office-space-1-5")}; min-width: ${tok("--office-target-size")}; min-height: ${tok("--office-target-size")};
1775
1775
  padding: 0 ${tok("--office-space-2")}; border: ${tok("--office-border-width")} solid transparent; border-radius: ${tok("--office-radius")};
1776
1776
  background: transparent; color: ${tok("--office-foreground")}; font: inherit; font-size: ${tok("--office-font-size-sm")};
1777
1777
  cursor: pointer; white-space: nowrap; }
1778
1778
  :host([variant="stacked"]) button { flex-direction: column; padding: ${tok("--office-space-1")} ${tok("--office-space-2")}; }
1779
1779
  button:hover:not(:disabled) { background: ${tok("--office-surface")}; }
1780
+ button:active:not(:disabled) { background: ${tok("--office-selected")}; }
1780
1781
  button:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; outline-offset: calc(${tok("--office-focus-offset")} / 2); }
1781
1782
  button[aria-pressed="true"], button[aria-expanded="true"] { border-color: ${tok("--office-accent")};
1782
1783
  background: ${tok("--office-surface")}; }
1784
+ :host([active]) button { background: ${tok("--office-command-active")}; color: ${tok("--office-accent")}; }
1783
1785
  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; }
1786
+ svg { width: ${tok("--office-icon-size-ml")}; height: ${tok("--office-icon-size-ml")}; flex: none; color: ${tok("--office-command-icon-color")};
1787
+ fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; }
1788
+ svg.glyph:not([data-painted]) { display: none; }
1789
+ .label[hidden], .caret[hidden], .badge[hidden] { display: none; }
1790
+ /* Office trails a menu chevron after the last line of the label. */
1791
+ .caret { display: inline-block; width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")};
1792
+ margin-inline-start: ${tok("--office-space-0")}; vertical-align: middle; color: ${tok("--office-muted-foreground")}; }
1793
+ .badge { position: absolute; top: 0; inset-inline-end: 0; padding: 0 ${tok("--office-space-0")}; border-radius: ${tok("--office-radius-full")};
1794
+ background: ${tok("--office-accent")}; color: ${tok("--office-accent-foreground")}; font-size: ${tok("--office-font-size-3xs")}; }
1795
+ /* Large ribbon command: a big glyph above a two-line label. */
1796
+ :host([size="large"]) button { flex-direction: column; justify-content: flex-start; gap: ${tok("--office-space-0")};
1797
+ min-width: ${tok("--office-command-large-min-width")}; max-width: ${tok("--office-command-large-max-width")};
1798
+ height: ${tok("--office-command-large-height")}; padding: ${tok("--office-space-0")} ${tok("--office-space-1")};
1799
+ border: 0; white-space: normal; text-align: center; line-height: ${tok("--office-line-height-tight")}; }
1800
+ :host([size="large"]) svg.glyph { width: ${tok("--office-command-large-icon")}; height: ${tok("--office-command-large-icon")}; }
1801
+ /* Small ribbon command: icon and label in one row. */
1802
+ :host([size="small"]) button { justify-content: flex-start; min-width: 0; height: ${tok("--office-control-height")};
1803
+ min-height: ${tok("--office-control-height")}; padding: 0 ${tok("--office-space-1-5")}; border: 0; text-align: start; }
1804
+ :host([size="small"]) svg.glyph { width: ${tok("--office-command-small-icon")}; height: ${tok("--office-command-small-icon")}; }
1805
+ /* Tall, label-less tool tiles (pens, erasers). */
1806
+ :host([icon-only][tall]) button { min-width: ${tok("--office-command-tall-min-width")}; height: ${tok("--office-command-tall-height")}; padding: ${tok("--office-space-1")}; }
1807
+ :host([icon-only][tall]) svg.glyph { width: ${tok("--office-command-tall-icon")}; height: ${tok("--office-command-tall-icon")}; }
1808
+ :host([icon-only][tall][active]) button { box-shadow: inset 0 0 0 ${tok("--office-border-width")} ${tok("--office-accent")}; }
1809
+ @media (pointer: coarse), (max-width: 767px) {
1810
+ :host([size="small"]) button { min-height: ${tok("--office-target-size-touch")}; }
1811
+ :host([icon-only]) button { min-width: ${tok("--office-target-size-touch")}; }
1812
+ }
1787
1813
  @media (forced-colors: active) {
1788
1814
  button { border-color: ButtonText; color: ButtonText; background: ButtonFace; }
1815
+ svg { color: ButtonText; }
1789
1816
  button:disabled { color: GrayText; border-color: GrayText; }
1790
- button[aria-pressed="true"], button[aria-expanded="true"] { background: Highlight; color: HighlightText; }
1817
+ button[aria-pressed="true"], button[aria-expanded="true"], :host([active]) button { background: Highlight; color: HighlightText; }
1791
1818
  }
1792
1819
  `;
1793
1820
  var defineButton = definer("office-ui-button", () => {
1794
1821
  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
- ];
1822
+ static requestEvent = "office-command";
1823
+ static idAttribute = "command";
1824
+ static detailKey = "command";
1825
+ static get observedAttributes() {
1826
+ return [
1827
+ "label",
1828
+ "icon",
1829
+ "command",
1830
+ "disabled",
1831
+ "pressed",
1832
+ "expanded",
1833
+ "icon-only",
1834
+ "title",
1835
+ "keyshortcuts",
1836
+ "badge",
1837
+ "caret",
1838
+ this.idAttribute
1839
+ ];
1840
+ }
1806
1841
  button;
1807
1842
  svg;
1808
1843
  text;
1844
+ caret;
1845
+ badge;
1809
1846
  constructor() {
1810
1847
  super();
1848
+ const doc = this.ownerDocument;
1811
1849
  const root = this.attachShadow({ mode: "open", delegatesFocus: true });
1812
1850
  attachStyles(root, controlCss(CSS10));
1813
- this.button = this.ownerDocument.createElement("button");
1851
+ this.button = doc.createElement("button");
1814
1852
  this.button.type = "button";
1815
1853
  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);
1854
+ this.svg = createIconSvg(doc);
1855
+ this.svg.classList.add("glyph");
1856
+ this.text = doc.createElement("span");
1857
+ this.text.className = "label";
1858
+ this.caret = createIconSvg(doc);
1859
+ this.caret.classList.add("caret");
1860
+ paintIcon(this.caret, "chevronDown");
1861
+ this.badge = doc.createElement("span");
1862
+ this.badge.className = "badge";
1863
+ this.badge.setAttribute("aria-hidden", "true");
1864
+ this.button.append(this.svg, this.text, this.badge);
1819
1865
  root.append(this.button);
1866
+ this.button.addEventListener("keydown", (event) => {
1867
+ if ((event.key === " " || event.key === "Enter") && !event.ctrlKey && !event.metaKey && !event.altKey)
1868
+ event.stopPropagation();
1869
+ });
1820
1870
  this.button.addEventListener("click", () => {
1821
- const command = this.getAttribute("command");
1822
- if (!this.button.disabled && command) emit(this, "office-command", { command });
1871
+ const { requestEvent, idAttribute, detailKey } = this.constructor;
1872
+ const id = this.getAttribute(idAttribute);
1873
+ if (!this.button.disabled && id)
1874
+ this.dispatchEvent(
1875
+ new CustomEvent(requestEvent, {
1876
+ detail: { [detailKey]: id },
1877
+ bubbles: true,
1878
+ composed: true
1879
+ })
1880
+ );
1823
1881
  });
1824
1882
  }
1825
1883
  connectedCallback() {
@@ -1842,10 +1900,21 @@ var defineButton = definer("office-ui-button", () => {
1842
1900
  if (value === void 0) this.removeAttribute("pressed");
1843
1901
  else this.setAttribute("pressed", String(value));
1844
1902
  }
1903
+ /** The registered icon to draw; a product subclass may map names onto its glyph set. */
1904
+ iconName() {
1905
+ return this.getAttribute("icon");
1906
+ }
1907
+ /** Whether the trailing menu chevron shows. */
1908
+ showsCaret() {
1909
+ const caret = this.getAttribute("caret");
1910
+ return caret !== null && caret !== "false";
1911
+ }
1845
1912
  sync() {
1846
1913
  const label = this.getAttribute("label") ?? "";
1847
1914
  const iconOnly = this.hasAttribute("icon-only");
1848
1915
  this.text.textContent = label;
1916
+ this.text.append(this.caret);
1917
+ this.caret.toggleAttribute("hidden", !this.showsCaret());
1849
1918
  this.text.hidden = iconOnly;
1850
1919
  if (iconOnly && label) this.button.setAttribute("aria-label", label);
1851
1920
  else this.button.removeAttribute("aria-label");
@@ -1854,8 +1923,10 @@ var defineButton = definer("office-ui-button", () => {
1854
1923
  const shortcuts = this.getAttribute("keyshortcuts");
1855
1924
  if (shortcuts) this.button.setAttribute("aria-keyshortcuts", shortcuts);
1856
1925
  else this.button.removeAttribute("aria-keyshortcuts");
1857
- if (paintIcon(this.svg, this.getAttribute("icon"))) this.svg.setAttribute("data-painted", "");
1926
+ if (paintIcon(this.svg, this.iconName())) this.svg.setAttribute("data-painted", "");
1858
1927
  else this.svg.removeAttribute("data-painted");
1928
+ this.badge.textContent = this.getAttribute("badge") ?? "";
1929
+ this.badge.hidden = !this.badge.textContent;
1859
1930
  for (const attr of ["pressed", "expanded"]) {
1860
1931
  const value = this.getAttribute(attr);
1861
1932
  if (value === null) this.button.removeAttribute(`aria-${attr}`);
@@ -3037,41 +3108,90 @@ function text(doc, tag, content, className) {
3037
3108
  // src/ribbon.ts
3038
3109
  var GROUP_CSS = `
3039
3110
  :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")}; }
3111
+ .group { position: relative; box-sizing: border-box; display: flex; flex-direction: column; justify-content: space-between;
3112
+ min-height: ${tok("--office-ribbon-group-height")}; padding: ${tok("--office-ribbon-group-padding")}; }
3113
+ /* Office draws a hairline between groups; a product may stop it short of the top and bottom. */
3114
+ .group::after { content: ''; position: absolute; top: ${tok("--office-ribbon-separator-inset")}; bottom: ${tok("--office-ribbon-separator-inset")};
3115
+ inset-inline-end: 0; width: ${tok("--office-border-width")}; background: ${tok("--office-ribbon-separator")}; }
3116
+ :host(:last-child) .group::after { content: none; }
3117
+ .row { display: flex; align-items: flex-start; gap: ${tok("--office-space-1")}; flex: 1; }
3118
+ /* One-line groups centre their commands on the whole group box. */
3119
+ :host([data-compact-row]) .row { align-items: center; }
3120
+ /* Several small drop-down galleries stack in columns of three. */
3121
+ :host([data-stack]) .row { flex-direction: column; align-items: stretch; align-content: flex-start; flex-wrap: wrap;
3122
+ max-height: ${tok("--office-ribbon-stack-max-height")}; gap: ${tok("--office-space-0")}; }
3045
3123
  ::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")};
3124
+ .foot { position: relative; display: flex; align-items: center; justify-content: center; padding: ${tok("--office-ribbon-caption-padding")}; }
3125
+ .caption { color: ${tok("--office-muted-foreground")}; font-size: ${tok("--office-ribbon-caption-size")};
3048
3126
  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")}; }
3127
+ .launcher { position: absolute; inset-inline-end: ${tok("--office-launcher-inset-end")}; bottom: ${tok("--office-launcher-inset-bottom")}; display: inline-grid; place-items: center;
3128
+ width: ${tok("--office-launcher-width")}; height: ${tok("--office-launcher-height")}; padding: 0; border: 0; border-radius: ${tok("--office-radius-xs")};
3129
+ background: transparent; color: ${tok("--office-muted-foreground")}; cursor: pointer; }
3130
+ .launcher:hover:not(:disabled) { background: ${tok("--office-surface")}; color: ${tok("--office-foreground")}; }
3053
3131
  .launcher:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; }
3054
3132
  .launcher:disabled { opacity: .45; cursor: not-allowed; }
3055
3133
  .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; } }
3134
+ .launcher svg { width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")}; fill: none; stroke: currentColor;
3135
+ stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; }
3136
+ /* Collapsed (narrow window): the group is one button; its commands open in a popup. */
3137
+ .face { display: none; }
3138
+ :host([data-collapsed]) .face { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: ${tok("--office-space-0")};
3139
+ min-width: ${tok("--office-collapsed-group-min-width")}; height: ${tok("--office-command-large-height")}; padding: ${tok("--office-space-0")} ${tok("--office-space-1-5")};
3140
+ border: 0; border-radius: ${tok("--office-radius")}; background: transparent; color: ${tok("--office-foreground")}; font: inherit;
3141
+ font-size: ${tok("--office-font-size-sm")}; cursor: pointer; }
3142
+ :host([data-collapsed]) .face:hover, :host([data-open]) .face { background: ${tok("--office-selected")}; }
3143
+ .face:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; outline-offset: ${tok("--office-focus-offset")}; }
3144
+ .face svg { width: ${tok("--office-command-large-icon")}; height: ${tok("--office-command-large-icon")}; fill: none; stroke: currentColor;
3145
+ stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; color: ${tok("--office-command-icon-color")}; }
3146
+ .face svg.chev { width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")}; color: currentColor; }
3147
+ :host([data-collapsed]) .foot, :host([data-collapsed]) .row { display: none; }
3148
+ :host([data-collapsed][data-open]) .row { display: flex; flex-wrap: wrap; position: fixed; top: ${tok("--office-ribbon-collapse-y")};
3149
+ left: ${tok("--office-ribbon-collapse-x")}; z-index: ${tok("--office-z-popover")}; box-sizing: border-box;
3150
+ max-width: calc(100vw - 2 * ${tok("--office-space-2")}); padding: ${tok("--office-space-2")}; background: ${tok("--office-popover")};
3151
+ color: ${tok("--office-popover-foreground")}; border: ${tok("--office-border-width")} solid ${tok("--office-border")};
3152
+ border-radius: ${tok("--office-radius-md")}; box-shadow: ${tok("--office-shadow")}; }
3153
+ @media (forced-colors: active) {
3154
+ .group::after { background: CanvasText; } .caption, .launcher { color: CanvasText; }
3155
+ :host([data-collapsed][data-open]) .row { border-color: CanvasText; }
3156
+ }
3059
3157
  `;
3158
+ function tokenPx(el, name, fallback) {
3159
+ const value = el.ownerDocument.defaultView?.getComputedStyle(el).getPropertyValue(name).trim();
3160
+ const px = value?.endsWith("px") ? parseFloat(value) : NaN;
3161
+ return Number.isFinite(px) ? px : fallback;
3162
+ }
3060
3163
  var defineRibbonGroup = definer("office-ui-ribbon-group", () => {
3061
3164
  class OfficeUiRibbonGroup extends HTMLElement {
3062
- static observedAttributes = ["label", "launcher", "launcher-disabled"];
3165
+ static launcherEvent = "office-command";
3166
+ static collapseEvent = "office-ribbon-collapse-toggle";
3167
+ static observedAttributes = [
3168
+ "label",
3169
+ "launcher",
3170
+ "launcher-label",
3171
+ "launcher-disabled",
3172
+ "icon",
3173
+ "data-open"
3174
+ ];
3063
3175
  caption;
3064
3176
  launcher;
3177
+ face;
3178
+ faceIcon;
3179
+ faceLabel;
3180
+ slotEl;
3181
+ observer;
3065
3182
  constructor() {
3066
3183
  super();
3067
3184
  const doc = this.ownerDocument;
3068
3185
  const root = this.attachShadow({ mode: "open" });
3069
3186
  attachStyles(root, controlCss(GROUP_CSS));
3187
+ const config3 = () => this.constructor;
3070
3188
  const group = doc.createElement("div");
3071
3189
  group.className = "group";
3072
3190
  const row = doc.createElement("div");
3073
3191
  row.className = "row";
3074
- row.append(doc.createElement("slot"));
3192
+ this.slotEl = doc.createElement("slot");
3193
+ this.slotEl.addEventListener("slotchange", () => this.observe());
3194
+ row.append(this.slotEl);
3075
3195
  this.caption = doc.createElement("span");
3076
3196
  this.caption.className = "caption";
3077
3197
  this.launcher = doc.createElement("button");
@@ -3081,30 +3201,94 @@ var defineRibbonGroup = definer("office-ui-ribbon-group", () => {
3081
3201
  paintIcon(glyph, "launcher");
3082
3202
  this.launcher.append(glyph);
3083
3203
  this.launcher.addEventListener("click", () => {
3084
- const command = this.getAttribute("launcher");
3085
- if (command && !this.launcher.disabled) emit(this, "office-command", { command });
3204
+ if (this.launcher.disabled) return;
3205
+ const detail = this.launcherDetail();
3206
+ if (detail)
3207
+ this.dispatchEvent(
3208
+ new CustomEvent(config3().launcherEvent, { detail, bubbles: true, composed: true })
3209
+ );
3086
3210
  });
3087
3211
  const foot = doc.createElement("div");
3088
3212
  foot.className = "foot";
3089
3213
  foot.append(this.caption, this.launcher);
3090
- group.append(row, foot);
3214
+ this.face = doc.createElement("button");
3215
+ this.face.type = "button";
3216
+ this.face.className = "face";
3217
+ this.face.setAttribute("aria-haspopup", "true");
3218
+ this.faceIcon = createIconSvg(doc);
3219
+ this.faceLabel = doc.createElement("span");
3220
+ const chevron = createIconSvg(doc);
3221
+ chevron.classList.add("chev");
3222
+ paintIcon(chevron, "chevronDown");
3223
+ this.face.append(this.faceIcon, this.faceLabel, chevron);
3224
+ this.face.addEventListener(
3225
+ "click",
3226
+ () => this.dispatchEvent(
3227
+ new CustomEvent(config3().collapseEvent, { bubbles: true, composed: true })
3228
+ )
3229
+ );
3230
+ group.append(this.face, row, foot);
3091
3231
  root.append(group);
3092
3232
  }
3093
3233
  connectedCallback() {
3094
3234
  this.setAttribute("role", "group");
3095
3235
  this.sync();
3236
+ this.observe();
3237
+ }
3238
+ disconnectedCallback() {
3239
+ this.observer?.disconnect();
3096
3240
  }
3097
3241
  attributeChangedCallback() {
3098
3242
  this.sync();
3099
3243
  }
3244
+ /** The launcher's event detail; null emits nothing. */
3245
+ launcherDetail() {
3246
+ const command = this.getAttribute("launcher");
3247
+ return command ? { command } : null;
3248
+ }
3249
+ /** The registered icon on the collapsed face; a product subclass may map names. */
3250
+ iconName() {
3251
+ return this.getAttribute("icon") ?? "grid";
3252
+ }
3253
+ observe() {
3254
+ this.observer?.disconnect();
3255
+ if (!this.isConnected) return;
3256
+ const Observer = this.ownerDocument.defaultView?.ResizeObserver;
3257
+ if (Observer) this.observer ??= new Observer(() => this.measure());
3258
+ for (const el of this.slotEl.assignedElements()) this.observer?.observe(el);
3259
+ this.measure();
3260
+ }
3261
+ measure() {
3262
+ const children = [...this.children];
3263
+ const stackable = children.filter((el) => {
3264
+ const gallery = el.matches('[mode="dropdown"]') ? el : el.querySelector('[mode="dropdown"]');
3265
+ return gallery !== null && !gallery.hasAttribute("data-command-large");
3266
+ });
3267
+ this.toggleAttribute(
3268
+ "data-stack",
3269
+ stackable.length > 1 && stackable.length === children.length
3270
+ );
3271
+ const tallest = Math.max(0, ...children.map((el) => el.getBoundingClientRect().height));
3272
+ if (tallest > 0)
3273
+ this.toggleAttribute(
3274
+ "data-compact-row",
3275
+ tallest <= tokenPx(this, "--office-ribbon-compact-row-max", 36)
3276
+ );
3277
+ }
3100
3278
  sync() {
3101
3279
  const label = this.getAttribute("label") ?? "";
3102
3280
  this.caption.textContent = label;
3103
3281
  this.setAttribute("aria-label", label);
3104
- this.launcher.hidden = !this.getAttribute("launcher");
3282
+ const launcherLabel = this.getAttribute("launcher-label") ?? `${label} options`;
3283
+ this.launcher.hidden = !this.hasAttribute("launcher");
3105
3284
  this.launcher.disabled = this.hasAttribute("launcher-disabled");
3106
- this.launcher.setAttribute("aria-label", `${label} options`);
3107
- this.launcher.title = `${label} options`;
3285
+ this.launcher.setAttribute("aria-label", launcherLabel);
3286
+ this.launcher.title = launcherLabel;
3287
+ this.faceLabel.textContent = label;
3288
+ this.face.title = label;
3289
+ this.face.setAttribute("aria-label", label);
3290
+ this.face.setAttribute("aria-expanded", String(this.hasAttribute("data-open")));
3291
+ paintIcon(this.faceIcon, this.iconName());
3108
3292
  }
3109
3293
  }
3110
3294
  return OfficeUiRibbonGroup;
@@ -3167,199 +3351,497 @@ var defineToolbar = definer("office-ui-toolbar", () => {
3167
3351
  return OfficeUiToolbar;
3168
3352
  });
3169
3353
 
3170
- // src/select.ts
3171
- var CSS15 = `
3172
- :host { display: inline-block; position: relative; vertical-align: middle; }
3173
- button { box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: ${tok("--office-space-2")};
3174
- width: 100%; min-height: ${tok("--office-target-size")}; padding: 0 ${tok("--office-space-2")}; font: inherit; font-size: ${tok("--office-font-size-sm")};
3175
- color: ${tok("--office-foreground")}; background: ${tok("--office-background")};
3176
- border: ${tok("--office-border-width")} solid ${tok("--office-border")}; border-radius: ${tok("--office-radius")}; cursor: pointer; }
3177
- button:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; outline-offset: calc(${tok("--office-focus-offset")} / 2); }
3178
- :host([disabled]) button { opacity: .5; cursor: not-allowed; }
3179
- ul { position: absolute; z-index: 10; inset-inline: 0; top: 100%; margin: ${tok("--office-space-0")} 0 0; padding: ${tok("--office-space-0")}; list-style: none;
3180
- max-height: ${tok("--office-select-max-height")}; overflow: auto; background: ${tok("--office-background")};
3181
- border: ${tok("--office-border-width")} solid ${tok("--office-border")}; border-radius: ${tok("--office-radius")}; }
3182
- ul[hidden] { display: none; }
3183
- li { display: flex; align-items: center; min-height: ${tok("--office-target-size")}; padding: 0 ${tok("--office-space-2")};
3184
- font-size: ${tok("--office-font-size-sm")}; color: ${tok("--office-foreground")}; cursor: pointer; border-radius: ${tok("--office-radius-sm")}; }
3185
- li[aria-selected="true"] { font-weight: 600; }
3186
- li[data-active] { background: ${tok("--office-surface")}; }
3187
- li[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
3354
+ // src/select-menu.ts
3355
+ var SELECT_OPTION_ATTRIBUTES = [
3356
+ "value",
3357
+ "disabled",
3358
+ "label",
3359
+ "selected",
3360
+ "hidden",
3361
+ "data-display-label",
3362
+ "data-description",
3363
+ "style"
3364
+ ];
3365
+ function collectSelectChoices(options) {
3366
+ return options.map((option) => {
3367
+ const parent = option.parentElement;
3368
+ const group = parent?.localName === "optgroup" ? parent : null;
3369
+ return {
3370
+ value: option.value,
3371
+ label: option.label || option.textContent?.trim() || "",
3372
+ disabled: option.disabled || Boolean(group?.disabled),
3373
+ hidden: Boolean(option.hidden) || Boolean(group?.hidden),
3374
+ group: group?.label ?? "",
3375
+ ...option.dataset.displayLabel ? { displayLabel: option.dataset.displayLabel } : {},
3376
+ ...option.dataset.description ? { description: option.dataset.description } : {},
3377
+ ...option.style.fontFamily ? { fontFamily: option.style.fontFamily } : {}
3378
+ };
3379
+ });
3380
+ }
3381
+ function renderSelectMenu(menu, choices, value) {
3382
+ const doc = menu.ownerDocument;
3383
+ const items = [];
3384
+ let previousGroup = "";
3385
+ choices.forEach((choice, index) => {
3386
+ if (choice.hidden) return;
3387
+ if (choice.group && choice.group !== previousGroup) {
3388
+ const heading = doc.createElement("div");
3389
+ heading.className = "group";
3390
+ heading.setAttribute("role", "presentation");
3391
+ heading.textContent = choice.group;
3392
+ items.push(heading);
3393
+ }
3394
+ previousGroup = choice.group;
3395
+ const item = doc.createElement("div");
3396
+ item.className = "option";
3397
+ item.setAttribute("role", "option");
3398
+ item.setAttribute("aria-selected", String(choice.value === value));
3399
+ item.setAttribute("aria-disabled", String(choice.disabled));
3400
+ item.id = `${menu.id}-${index}`;
3401
+ item.dataset.index = String(index);
3402
+ item.textContent = choice.displayLabel ?? choice.label;
3403
+ if (choice.fontFamily) item.style.fontFamily = choice.fontFamily;
3404
+ if (choice.description) {
3405
+ const description = doc.createElement("span");
3406
+ description.className = "description";
3407
+ description.textContent = choice.description;
3408
+ item.append(description);
3409
+ }
3410
+ items.push(item);
3411
+ });
3412
+ menu.replaceChildren(...items);
3413
+ }
3414
+ function tokenPx2(el, name, fallback) {
3415
+ const value = el.ownerDocument.defaultView?.getComputedStyle(el).getPropertyValue(name).trim();
3416
+ const px = value?.endsWith("px") ? parseFloat(value) : NaN;
3417
+ return Number.isFinite(px) ? px : fallback;
3418
+ }
3419
+ function positionSelectMenu(menu, trigger, host) {
3420
+ const view = menu.ownerDocument.defaultView;
3421
+ const rect = trigger.getBoundingClientRect();
3422
+ const margin = tokenPx2(host, "--office-space-2", 8);
3423
+ const gap = tokenPx2(host, "--office-space-1", 4);
3424
+ const width = Math.max(0, view.innerWidth - margin * 2);
3425
+ const below = Math.max(0, view.innerHeight - rect.bottom - gap - margin);
3426
+ const above = Math.max(0, rect.top - gap - margin);
3427
+ menu.style.minWidth = `${Math.min(rect.width, width)}px`;
3428
+ menu.style.maxWidth = `${width}px`;
3429
+ const cap = host.getAttribute("data-font-picker") === "family" ? tokenPx2(host, "--office-font-picker-max-height", 320) : tokenPx2(host, "--office-select-max-height", 240);
3430
+ menu.style.maxHeight = `${cap}px`;
3431
+ const preferred = menu.getBoundingClientRect().height;
3432
+ const flip = below < preferred && above > below;
3433
+ menu.style.maxHeight = `${Math.min(cap, flip ? above : below)}px`;
3434
+ const { height, width: menuWidth } = menu.getBoundingClientRect();
3435
+ menu.style.top = `${Math.max(margin, flip ? rect.top - gap - height : rect.bottom + gap)}px`;
3436
+ menu.style.left = `${Math.max(margin, Math.min(rect.left, view.innerWidth - menuWidth - margin))}px`;
3437
+ }
3438
+ function markSelectActive(menu, trigger, active, open) {
3439
+ for (const item of menu.querySelectorAll("[data-index]"))
3440
+ item.toggleAttribute("data-active", Number(item.dataset.index) === active && open);
3441
+ if (open && active >= 0) trigger.setAttribute("aria-activedescendant", `${menu.id}-${active}`);
3442
+ menu.querySelector("[data-active]")?.scrollIntoView?.({ block: "nearest" });
3443
+ }
3444
+ function nextSelectActive(choices, active, step) {
3445
+ for (let i = 0; i < choices.length; i++) {
3446
+ active = (active + step + choices.length) % choices.length;
3447
+ if (!choices[active].disabled && !choices[active].hidden) return active;
3448
+ }
3449
+ return active;
3450
+ }
3451
+ var SELECT_PAGE_SIZE = 8;
3452
+ function pageSelectActive(choices, active, direction) {
3453
+ const usable = (index) => !choices[index]?.disabled && !choices[index]?.hidden;
3454
+ const last = choices.length - 1;
3455
+ const target = Math.max(0, Math.min(last, Math.max(active, 0) + direction * SELECT_PAGE_SIZE));
3456
+ for (let index = target; index >= 0 && index <= last; index -= direction)
3457
+ if (usable(index)) return index;
3458
+ return active;
3459
+ }
3460
+ function createSelectTrigger(doc) {
3461
+ const trigger = doc.createElement("button");
3462
+ trigger.type = "button";
3463
+ trigger.setAttribute("part", "trigger");
3464
+ trigger.setAttribute("role", "combobox");
3465
+ trigger.setAttribute("aria-haspopup", "listbox");
3466
+ trigger.setAttribute("aria-expanded", "false");
3467
+ const icon2 = doc.createElement("slot");
3468
+ icon2.name = "icon";
3469
+ const text2 = doc.createElement("span");
3470
+ text2.className = "value";
3471
+ text2.setAttribute("part", "value");
3472
+ const chevron = doc.createElement("span");
3473
+ chevron.className = "chevron";
3474
+ chevron.setAttribute("part", "indicator");
3475
+ chevron.setAttribute("aria-hidden", "true");
3476
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
3477
+ svg.setAttribute("viewBox", "0 0 24 24");
3478
+ svg.setAttribute("fill", "none");
3479
+ svg.setAttribute("stroke", "currentColor");
3480
+ svg.setAttribute("stroke-width", "2");
3481
+ svg.setAttribute("stroke-linecap", "round");
3482
+ svg.setAttribute("stroke-linejoin", "round");
3483
+ const path = doc.createElementNS(svg.namespaceURI, "path");
3484
+ path.setAttribute("d", "m6 9 6 6 6-6");
3485
+ svg.append(path);
3486
+ chevron.append(svg);
3487
+ trigger.append(icon2, text2, chevron);
3488
+ return { trigger, text: text2 };
3489
+ }
3490
+ function prependSelectCustomSlot(host, menu) {
3491
+ if (!host.querySelector('[slot="custom"]')) return;
3492
+ const custom = host.ownerDocument.createElement("slot");
3493
+ custom.name = "custom";
3494
+ menu.prepend(custom);
3495
+ }
3496
+
3497
+ // src/select-styles.ts
3498
+ var SELECT_CSS = `
3499
+ :host { display: inline-block; box-sizing: border-box; min-width: 0; max-width: 100%;
3500
+ color: ${tok("--office-foreground")}; font: inherit; font-size: ${tok("--office-font-size-sm")}; vertical-align: middle; }
3501
+ :host([disabled]) { opacity: .5; cursor: not-allowed; }
3502
+ button { box-sizing: border-box; width: 100%; min-height: ${tok("--office-field-height")}; display: flex; align-items: center;
3503
+ justify-content: space-between; gap: ${tok("--office-space-2-5")}; padding: ${tok("--office-space-1")} ${tok("--office-space-1-5")};
3504
+ border: ${tok("--office-border-width")} solid ${tok("--office-field-border")}; border-radius: ${tok("--office-field-radius")};
3505
+ background: ${tok("--office-select-background")}; color: inherit; font: inherit; font-size: inherit; text-align: start; cursor: pointer; }
3506
+ button:focus-visible { ${FOCUS_RING} }
3507
+ button:disabled { cursor: not-allowed; }
3508
+ .value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3509
+ .chevron svg { display: none; }
3510
+ .chevron { flex: none; width: ${tok("--office-chevron-size")}; height: ${tok("--office-chevron-size")};
3511
+ margin: 0 ${tok("--office-space-0")} ${tok("--office-space-0")} 0;
3512
+ border-right: calc(${tok("--office-border-width")} * 1.5) solid currentColor;
3513
+ border-bottom: calc(${tok("--office-border-width")} * 1.5) solid currentColor; transform: rotate(45deg); }
3514
+ :host([open]) .chevron { margin: ${tok("--office-space-0")} ${tok("--office-space-0")} 0 0; transform: rotate(225deg); }
3515
+ .menu { box-sizing: border-box; position: fixed; margin: 0; padding: ${tok("--office-space-1")};
3516
+ min-width: ${tok("--office-select-menu-min-width")}; max-height: min(${tok("--office-select-max-height")}, 50vh); overflow: auto;
3517
+ border: ${tok("--office-border-width")} solid ${tok("--office-field-border")}; border-radius: ${tok("--office-field-radius")};
3518
+ background: ${tok("--office-popover")}; color: ${tok("--office-popover-foreground")}; box-shadow: ${tok("--office-shadow")};
3519
+ font: inherit; font-size: inherit; }
3520
+ .menu:not(:popover-open):not([data-fallback-open]) { display: none; }
3521
+ .option { display: block; box-sizing: border-box; width: 100%; padding: ${tok("--office-space-1-5")} ${tok("--office-space-2")};
3522
+ border-radius: ${tok("--office-radius-sm")}; cursor: pointer; white-space: nowrap; }
3523
+ .group { padding: ${tok("--office-space-1-5")} ${tok("--office-space-2")} ${tok("--office-space-0")};
3524
+ color: ${tok("--office-muted-foreground")}; font-size: .9em; font-weight: ${tok("--office-font-weight-bold")}; }
3525
+ .option:not([aria-disabled="true"]):hover, .option[data-active]:not([aria-selected="true"]) {
3526
+ background: ${tok("--office-select-option-active")}; }
3527
+ .option[aria-selected="true"] { background: ${tok("--office-accent")}; color: ${tok("--office-accent-foreground")}; }
3528
+ .option[aria-selected="true"]:hover { filter: brightness(1.08); }
3529
+ .option[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
3530
+ .description { flex: none; color: ${tok("--office-muted-foreground")}; font-size: ${tok("--office-font-size-2xs")}; }
3531
+
3532
+ :host([variant^="ribbon-"][disabled]) { opacity: .4; }
3533
+ :host([variant^="ribbon-"]) button { height: ${tok("--office-ribbon-combo-height")}; min-height: ${tok("--office-ribbon-combo-height")};
3534
+ padding: ${tok("--office-space-1")} ${tok("--office-space-2")}; gap: ${tok("--office-space-1")};
3535
+ border: ${tok("--office-border-width")} solid color-mix(in oklab, ${tok("--office-border")} 60%, transparent);
3536
+ border-radius: ${tok("--office-radius-sm")}; font-size: ${tok("--office-font-size-sm")};
3537
+ background: color-mix(in oklab, ${tok("--office-background")} 60%, transparent); color: ${tok("--office-foreground")}; }
3538
+ :host([variant^="ribbon-"]) .value { flex: 1; min-width: 0; }
3539
+ :host([variant^="ribbon-"]) .chevron { width: ${tok("--office-icon-size-md")}; height: ${tok("--office-icon-size-md")}; margin: 0; border: 0; transform: none; }
3540
+ :host([variant^="ribbon-"]) .chevron svg { display: block; width: ${tok("--office-icon-size-md")}; height: ${tok("--office-icon-size-md")}; }
3541
+ :host([variant="ribbon-font"]) .chevron { color: ${tok("--office-muted-foreground")}; }
3542
+ :host([variant="ribbon-font"][data-font-picker="size"]) button { padding-inline: ${tok("--office-space-1-5")}; }
3543
+ :host([variant="ribbon-icon"]) button { width: ${tok("--office-ribbon-icon-select-width")}; gap: ${tok("--office-space-1-5")};
3544
+ padding: ${tok("--office-space-1-5")} ${tok("--office-space-2-5")}; border: 0; background: ${tok("--office-surface")}; justify-content: center; }
3545
+ :host([variant="ribbon-icon"]) :is(.value, .chevron) { display: none; }
3546
+ ::slotted(svg[slot="icon"]) { display: block; width: ${tok("--office-icon-size-md")}; height: ${tok("--office-icon-size-md")}; flex: none; }
3547
+ :host([variant^="ribbon-"]) button:hover:not(:disabled) { background: ${tok("--office-selected")}; }
3548
+ :host([variant^="ribbon-"]) .menu { width: ${tok("--office-select-ribbon-menu-width")}; padding: ${tok("--office-space-1")} 0;
3549
+ border-radius: ${tok("--office-radius-lg")}; font-size: ${tok("--office-font-size-sm")}; }
3550
+ :host([data-font-picker="family"]) .menu { width: ${tok("--office-font-picker-width")}; }
3551
+ :host([variant^="ribbon-"]) .option { display: flex; justify-content: space-between; gap: ${tok("--office-space-3")};
3552
+ padding: ${tok("--office-space-1-5")} ${tok("--office-space-3")}; border-radius: 0; }
3553
+ :host([variant^="ribbon-"]) .option[aria-selected="true"] { background: transparent; color: ${tok("--office-foreground")}; }
3554
+ :host([variant^="ribbon-"]) .option:hover, :host([variant^="ribbon-"]) .option[data-active] { background: ${tok("--office-surface")}; filter: none; }
3555
+ :host([variant^="ribbon-"]) .group { padding: ${tok("--office-space-2")} ${tok("--office-space-3")} ${tok("--office-space-1")};
3556
+ text-transform: uppercase; letter-spacing: .02em; font-size: ${tok("--office-font-size-2xs")}; }
3557
+ @media (pointer: coarse), (max-width: 767px) {
3558
+ button, :host([variant^="ribbon-"]) button { min-height: ${tok("--office-target-size-touch")}; }
3559
+ :host([variant^="ribbon-"]) button { height: ${tok("--office-target-size-touch")}; }
3560
+ }
3188
3561
  @media (forced-colors: active) {
3189
- button, ul { border-color: CanvasText; background: Canvas; color: CanvasText; }
3190
- li[data-active] { background: Highlight; color: HighlightText; }
3562
+ button, .menu { border-color: CanvasText; background: Canvas; color: CanvasText; }
3563
+ .option[aria-selected="true"], .option[data-active], .option:not([aria-disabled="true"]):hover {
3564
+ background: Highlight; color: HighlightText; }
3191
3565
  }
3192
3566
  `;
3567
+
3568
+ // src/select.ts
3193
3569
  var uid2 = 0;
3194
3570
  var defineSelect = definer("office-ui-select", () => {
3195
3571
  class OfficeUiSelect extends HTMLElement {
3572
+ static formAssociated = true;
3196
3573
  static observedAttributes = ["value", "disabled", "aria-label"];
3197
- opts = [];
3198
- current = "";
3199
- active = -1;
3200
- typed = "";
3201
- typedAt = 0;
3574
+ internals;
3202
3575
  trigger;
3203
- list;
3204
- label;
3205
- domId = `office-ui-select-${++uid2}`;
3576
+ text;
3577
+ menu;
3578
+ observer;
3579
+ refreshFrame = 0;
3580
+ data = [];
3581
+ choices = [];
3582
+ active = -1;
3583
+ isOpen = false;
3584
+ search = "";
3585
+ searchTimer = 0;
3586
+ defaultValue = "";
3587
+ menuKey = "";
3206
3588
  constructor() {
3207
3589
  super();
3590
+ try {
3591
+ this.internals = this.attachInternals();
3592
+ } catch {
3593
+ this.internals = void 0;
3594
+ }
3208
3595
  const doc = this.ownerDocument;
3209
3596
  const root = this.attachShadow({ mode: "open", delegatesFocus: true });
3210
- attachStyles(root, controlCss(CSS15));
3211
- this.trigger = doc.createElement("button");
3212
- this.trigger.type = "button";
3213
- this.trigger.setAttribute("role", "combobox");
3214
- this.trigger.setAttribute("aria-haspopup", "listbox");
3215
- this.trigger.setAttribute("aria-expanded", "false");
3216
- this.trigger.setAttribute("aria-controls", this.domId);
3217
- this.label = doc.createElement("span");
3218
- const caret = doc.createElement("span");
3219
- caret.setAttribute("aria-hidden", "true");
3220
- caret.textContent = "\u25BE";
3221
- this.trigger.append(this.label, caret);
3222
- this.list = doc.createElement("ul");
3223
- this.list.id = this.domId;
3224
- this.list.setAttribute("role", "listbox");
3225
- this.list.hidden = true;
3226
- root.append(this.trigger, this.list);
3227
- this.trigger.addEventListener("click", () => this.isOpen ? this.close() : this.open());
3228
- this.trigger.addEventListener("keydown", (event) => this.onKey(event));
3229
- this.trigger.addEventListener("blur", () => this.close());
3230
- this.list.addEventListener("mousedown", (event) => event.preventDefault());
3231
- this.list.addEventListener("click", (event) => {
3232
- const item = event.target.closest("li");
3233
- if (item) this.choose(Number(item.dataset.index));
3597
+ attachStyles(root, controlCss(SELECT_CSS));
3598
+ ({ trigger: this.trigger, text: this.text } = createSelectTrigger(doc));
3599
+ this.menu = doc.createElement("div");
3600
+ this.menu.className = "menu";
3601
+ this.menu.id = `office-ui-select-options-${++uid2}`;
3602
+ this.menu.setAttribute("role", "listbox");
3603
+ this.menu.setAttribute("popover", "manual");
3604
+ this.menu.setAttribute("part", "popup");
3605
+ this.trigger.setAttribute("aria-controls", this.menu.id);
3606
+ root.append(this.trigger, this.menu);
3607
+ this.trigger.addEventListener("click", () => this.isOpen ? this.close() : this.show());
3608
+ this.trigger.addEventListener("keydown", (event) => this.onKeyDown(event));
3609
+ this.menu.addEventListener("pointerdown", (event) => {
3610
+ if (event.target.closest("[data-index]")) event.preventDefault();
3234
3611
  });
3612
+ this.menu.addEventListener("click", (event) => {
3613
+ const target = event.target.closest("[data-index]");
3614
+ if (target) this.commit(Number(target.dataset.index));
3615
+ });
3616
+ const Observer = doc.defaultView?.MutationObserver;
3617
+ this.observer = Observer ? new Observer(() => {
3618
+ const view = this.ownerDocument.defaultView;
3619
+ if (this.refreshFrame || !view) return;
3620
+ this.refreshFrame = view.requestAnimationFrame(() => {
3621
+ this.refreshFrame = 0;
3622
+ this.refresh();
3623
+ });
3624
+ }) : void 0;
3235
3625
  }
3236
3626
  connectedCallback() {
3237
- this.sync();
3627
+ this.defaultValue = this.getAttribute("value") ?? "";
3628
+ this.observer?.observe(this, {
3629
+ childList: true,
3630
+ subtree: true,
3631
+ attributes: true,
3632
+ characterData: true,
3633
+ attributeFilter: SELECT_OPTION_ATTRIBUTES
3634
+ });
3635
+ this.refresh();
3238
3636
  }
3239
- attributeChangedCallback(name, _old, value) {
3240
- if (name === "value") this.current = value ?? "";
3241
- this.sync();
3637
+ disconnectedCallback() {
3638
+ this.close();
3639
+ const view = this.ownerDocument.defaultView;
3640
+ view?.clearTimeout(this.searchTimer);
3641
+ this.searchTimer = 0;
3642
+ this.search = "";
3643
+ this.observer?.disconnect();
3644
+ if (this.refreshFrame) view?.cancelAnimationFrame(this.refreshFrame);
3645
+ this.refreshFrame = 0;
3242
3646
  }
3647
+ attributeChangedCallback() {
3648
+ this.refresh();
3649
+ }
3650
+ /** The `<option>` children, or the programmatic options when there are none. */
3243
3651
  get options() {
3244
- return this.opts;
3652
+ const children = [...this.querySelectorAll("option")];
3653
+ return children.length ? children : this.data;
3245
3654
  }
3246
3655
  set options(next) {
3247
- this.opts = next.map((option) => ({ ...option }));
3248
- this.sync();
3656
+ this.data = [...next];
3657
+ this.refresh();
3249
3658
  }
3250
3659
  get value() {
3251
- return this.current;
3660
+ const requested = this.getAttribute("value");
3661
+ const choices = this.choices.length ? this.choices : this.collect();
3662
+ if (requested !== null && this.getAttribute("variant") === "ribbon-font") return requested;
3663
+ if (requested !== null && (!choices.length || choices.some((c) => c.value === requested)))
3664
+ return requested;
3665
+ return [...this.querySelectorAll("option")].find((option) => option.selected)?.value ?? choices.find((choice) => !choice.disabled && !choice.hidden)?.value ?? choices[0]?.value ?? requested ?? "";
3252
3666
  }
3253
3667
  set value(next) {
3254
- this.current = String(next);
3255
- this.sync();
3668
+ this.setAttribute("value", String(next ?? ""));
3256
3669
  }
3257
3670
  get selectedIndex() {
3258
- return this.opts.findIndex((option) => option.value === this.current);
3671
+ return this.options.findIndex((option) => option.value === this.value);
3259
3672
  }
3260
3673
  set selectedIndex(index) {
3261
- const option = this.opts[index];
3262
- if (option) this.value = option.value;
3674
+ this.value = this.options[index]?.value ?? "";
3263
3675
  }
3264
3676
  get disabled() {
3265
3677
  return this.hasAttribute("disabled");
3266
3678
  }
3267
- set disabled(value) {
3268
- this.toggleAttribute("disabled", Boolean(value));
3679
+ set disabled(next) {
3680
+ this.toggleAttribute("disabled", Boolean(next));
3269
3681
  }
3270
- get isOpen() {
3271
- return !this.list.hidden;
3682
+ focus(options) {
3683
+ this.trigger.focus(options);
3272
3684
  }
3273
- open() {
3274
- if (this.disabled || this.isOpen) return;
3275
- this.list.hidden = false;
3276
- this.trigger.setAttribute("aria-expanded", "true");
3277
- this.setActive(this.selectedIndex >= 0 ? this.selectedIndex : this.step(-1, 1));
3685
+ formResetCallback() {
3686
+ this.value = this.defaultValue;
3278
3687
  }
3279
- close() {
3280
- this.list.hidden = true;
3281
- this.trigger.setAttribute("aria-expanded", "false");
3282
- this.trigger.removeAttribute("aria-activedescendant");
3283
- this.active = -1;
3688
+ formDisabledCallback(disabled) {
3689
+ this.disabled = disabled;
3690
+ }
3691
+ collect() {
3692
+ const children = [...this.querySelectorAll("option")];
3693
+ if (children.length) return collectSelectChoices(children);
3694
+ return this.data.map((option) => ({
3695
+ value: option.value,
3696
+ label: option.label,
3697
+ disabled: option.disabled === true,
3698
+ hidden: false,
3699
+ group: ""
3700
+ }));
3284
3701
  }
3285
- step(from, dir) {
3286
- for (let i = from + dir; i >= 0 && i < this.opts.length; i += dir) {
3287
- if (!this.opts[i]?.disabled) return i;
3702
+ refresh() {
3703
+ if (!this.trigger) return;
3704
+ this.choices = this.collect();
3705
+ if (!this.hasAttribute("value") && this.choices.length) {
3706
+ const children = [...this.querySelectorAll("option")];
3707
+ this.setAttribute(
3708
+ "value",
3709
+ children.find((option) => option.selected)?.value ?? this.choices.find((choice) => !choice.disabled && !choice.hidden)?.value ?? this.choices[0].value
3710
+ );
3711
+ return;
3288
3712
  }
3289
- return from;
3713
+ const shown = this.choices.find((choice) => choice.value === this.value);
3714
+ this.text.textContent = shown?.displayLabel ?? shown?.label ?? this.value;
3715
+ this.trigger.disabled = this.disabled;
3716
+ if (this.disabled) this.close();
3717
+ const name = this.getAttribute("aria-label");
3718
+ if (name) this.trigger.setAttribute("aria-label", name);
3719
+ else this.trigger.removeAttribute("aria-label");
3720
+ this.internals?.setFormValue?.(this.disabled ? null : this.value);
3721
+ if ((this.isOpen || this.menu.childElementCount) && this.menuKey !== this.currentMenuKey())
3722
+ this.renderMenu();
3290
3723
  }
3291
- setActive(index) {
3292
- this.active = index;
3293
- this.list.querySelectorAll("li").forEach((item, i) => item.toggleAttribute("data-active", i === index));
3294
- if (index >= 0) this.trigger.setAttribute("aria-activedescendant", `${this.domId}-${index}`);
3724
+ currentMenuKey() {
3725
+ return `${this.value}|${JSON.stringify(this.choices)}`;
3295
3726
  }
3296
- choose(index) {
3297
- const option = this.opts[index];
3298
- if (!option || option.disabled) return;
3299
- const changed = option.value !== this.current;
3300
- this.value = option.value;
3727
+ renderMenu() {
3728
+ this.menuKey = this.currentMenuKey();
3729
+ renderSelectMenu(this.menu, this.choices, this.value);
3730
+ prependSelectCustomSlot(this, this.menu);
3731
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3732
+ }
3733
+ show() {
3734
+ if (this.disabled || this.isOpen || !this.choices.some((c) => !c.disabled && !c.hidden))
3735
+ return;
3736
+ this.isOpen = true;
3737
+ this.setAttribute("open", "");
3738
+ const selected = this.choices.findIndex(
3739
+ (choice) => choice.value === this.value && !choice.disabled && !choice.hidden
3740
+ );
3741
+ this.active = selected >= 0 ? selected : this.choices.findIndex((choice) => !choice.disabled && !choice.hidden);
3742
+ this.renderMenu();
3743
+ if (typeof this.menu.showPopover === "function") this.menu.showPopover();
3744
+ else this.menu.dataset.fallbackOpen = "";
3745
+ this.trigger.setAttribute("aria-expanded", "true");
3746
+ this.position();
3747
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3748
+ const view = this.ownerDocument.defaultView;
3749
+ this.ownerDocument.addEventListener("pointerdown", this.onOutside, true);
3750
+ view?.addEventListener("resize", this.position);
3751
+ view?.addEventListener("scroll", this.position, true);
3752
+ }
3753
+ close() {
3754
+ if (!this.isOpen) return;
3755
+ this.isOpen = false;
3756
+ const view = this.ownerDocument.defaultView;
3757
+ view?.clearTimeout(this.searchTimer);
3758
+ this.search = "";
3759
+ this.removeAttribute("open");
3760
+ try {
3761
+ if (this.menu.matches(":popover-open")) this.menu.hidePopover();
3762
+ } catch {
3763
+ }
3764
+ delete this.menu.dataset.fallbackOpen;
3765
+ this.trigger.setAttribute("aria-expanded", "false");
3766
+ this.trigger.removeAttribute("aria-activedescendant");
3767
+ this.ownerDocument.removeEventListener("pointerdown", this.onOutside, true);
3768
+ view?.removeEventListener("resize", this.position);
3769
+ view?.removeEventListener("scroll", this.position, true);
3770
+ }
3771
+ onOutside = (event) => {
3772
+ if (!event.composedPath().includes(this)) this.close();
3773
+ };
3774
+ position = () => {
3775
+ if (this.isOpen) positionSelectMenu(this.menu, this.trigger, this);
3776
+ };
3777
+ move(step) {
3778
+ if (!this.isOpen) this.show();
3779
+ if (!this.isOpen) return;
3780
+ this.active = nextSelectActive(this.choices, this.active, step);
3781
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3782
+ }
3783
+ commit(index) {
3784
+ const choice = this.choices[index];
3785
+ if (!choice || choice.disabled || choice.hidden) return;
3786
+ const changed = this.value !== choice.value;
3787
+ this.value = choice.value;
3301
3788
  this.close();
3789
+ this.trigger.focus();
3302
3790
  if (changed) {
3303
3791
  this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
3304
3792
  this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
3305
3793
  }
3306
3794
  }
3307
- onKey(event) {
3795
+ onKeyDown(event) {
3308
3796
  if (this.disabled) return;
3309
- const key = event.key;
3310
- if (key === "ArrowDown" || key === "ArrowUp") {
3311
- event.preventDefault();
3312
- if (!this.isOpen) return this.open();
3313
- this.setActive(this.step(this.active, key === "ArrowDown" ? 1 : -1));
3314
- } else if (key === "Home" || key === "End") {
3315
- event.preventDefault();
3316
- if (!this.isOpen) this.open();
3317
- this.setActive(key === "Home" ? this.step(-1, 1) : this.step(this.opts.length, -1));
3318
- } else if (key === "Enter" || key === " ") {
3797
+ const stop = () => {
3319
3798
  event.preventDefault();
3320
- if (this.isOpen) this.choose(this.active);
3321
- else this.open();
3322
- } else if (key === "Escape") {
3323
- if (this.isOpen) {
3324
- event.preventDefault();
3325
- event.stopPropagation();
3326
- this.close();
3327
- }
3328
- } else if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
3329
- const now = Date.now();
3330
- this.typed = now - this.typedAt > 700 ? key : this.typed + key;
3331
- this.typedAt = now;
3332
- const needle = this.typed.toLowerCase();
3333
- const index = this.opts.findIndex(
3334
- (option) => !option.disabled && option.label.toLowerCase().startsWith(needle)
3799
+ event.stopPropagation();
3800
+ };
3801
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
3802
+ stop();
3803
+ this.move(event.key === "ArrowDown" ? 1 : -1);
3804
+ } else if (event.key === "Home" || event.key === "End") {
3805
+ stop();
3806
+ this.show();
3807
+ this.active = event.key === "Home" ? nextSelectActive(this.choices, -1, 1) : nextSelectActive(this.choices, 0, -1);
3808
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3809
+ } else if (event.key === "PageUp" || event.key === "PageDown") {
3810
+ stop();
3811
+ this.show();
3812
+ this.active = pageSelectActive(
3813
+ this.choices,
3814
+ this.active,
3815
+ event.key === "PageDown" ? 1 : -1
3816
+ );
3817
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3818
+ } else if (event.key === "Enter" || event.key === " ") {
3819
+ stop();
3820
+ if (this.isOpen) this.commit(this.active);
3821
+ else this.show();
3822
+ } else if (event.key === "Escape" && this.isOpen) {
3823
+ stop();
3824
+ this.close();
3825
+ this.trigger.focus();
3826
+ } else if (event.key === "Tab") this.close();
3827
+ else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
3828
+ event.stopPropagation();
3829
+ const view = this.ownerDocument.defaultView;
3830
+ this.search += event.key.toLocaleLowerCase();
3831
+ view?.clearTimeout(this.searchTimer);
3832
+ this.searchTimer = view?.setTimeout(() => {
3833
+ this.search = "";
3834
+ }, 700) ?? 0;
3835
+ this.show();
3836
+ const index = this.choices.findIndex(
3837
+ (choice) => !choice.disabled && !choice.hidden && choice.label.toLocaleLowerCase().startsWith(this.search)
3335
3838
  );
3336
3839
  if (index >= 0) {
3337
- if (this.isOpen) this.setActive(index);
3338
- else this.choose(index);
3840
+ this.active = index;
3841
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3339
3842
  }
3340
3843
  }
3341
3844
  }
3342
- sync() {
3343
- this.trigger.disabled = this.disabled;
3344
- this.setAttribute("aria-disabled", String(this.disabled));
3345
- const name = this.getAttribute("aria-label");
3346
- if (name) this.trigger.setAttribute("aria-label", name);
3347
- else this.trigger.removeAttribute("aria-label");
3348
- this.label.textContent = this.opts[this.selectedIndex]?.label ?? "";
3349
- this.list.replaceChildren(
3350
- ...this.opts.map((option, index) => {
3351
- const item = this.ownerDocument.createElement("li");
3352
- item.id = `${this.domId}-${index}`;
3353
- item.dataset.index = String(index);
3354
- item.setAttribute("role", "option");
3355
- item.setAttribute("aria-selected", String(option.value === this.current));
3356
- if (option.disabled) item.setAttribute("aria-disabled", "true");
3357
- item.textContent = option.label;
3358
- return item;
3359
- })
3360
- );
3361
- if (this.isOpen) this.setActive(Math.min(this.active, this.opts.length - 1));
3362
- }
3363
3845
  }
3364
3846
  return OfficeUiSelect;
3365
3847
  });
@@ -3458,7 +3940,7 @@ var defineStatusItem = definer("office-ui-status-item", () => {
3458
3940
  });
3459
3941
 
3460
3942
  // src/tab-strip.ts
3461
- var CSS16 = `
3943
+ var CSS15 = `
3462
3944
  :host { display: flex; align-items: stretch; min-width: 0; min-height: ${tok("--office-tab-height")}; gap: ${tok("--office-space-0")}; padding: 0 ${tok("--office-space-1-5")};
3463
3945
  background: ${tok("--office-surface")}; color: ${tok("--office-muted-foreground")};
3464
3946
  border-top: ${tok("--office-border-width")} solid ${tok("--office-border")}; font-size: ${tok("--office-font-size-sm")};
@@ -3505,7 +3987,7 @@ var defineTabStrip = definer("office-ui-tab-strip", () => {
3505
3987
  super();
3506
3988
  const doc = this.ownerDocument;
3507
3989
  const root = this.attachShadow({ mode: "open" });
3508
- attachStyles(root, controlCss(CSS16));
3990
+ attachStyles(root, controlCss(CSS15));
3509
3991
  const step = (name, icon2) => {
3510
3992
  const button = doc.createElement("button");
3511
3993
  button.type = "button";
@@ -3644,7 +4126,7 @@ var defineTabStrip = definer("office-ui-tab-strip", () => {
3644
4126
  });
3645
4127
 
3646
4128
  // src/zoom-slider.ts
3647
- var CSS17 = `
4129
+ var CSS16 = `
3648
4130
  :host { display: inline-flex; align-items: center; gap: ${tok("--office-space-0")}; font-size: ${tok("--office-font-size-xs")};
3649
4131
  color: ${tok("--office-foreground")}; font-family: ${tok("--office-font")}; }
3650
4132
  button { box-sizing: border-box; display: inline-grid; place-items: center; min-width: ${tok("--office-target-size")};
@@ -3680,7 +4162,7 @@ var defineZoomSlider = definer("office-ui-zoom-slider", () => {
3680
4162
  super();
3681
4163
  const doc = this.ownerDocument;
3682
4164
  const root = this.attachShadow({ mode: "open" });
3683
- attachStyles(root, controlCss(CSS17));
4165
+ attachStyles(root, controlCss(CSS16));
3684
4166
  const button = (name, text2) => {
3685
4167
  const element = doc.createElement("button");
3686
4168
  element.type = "button";