ooxml-ui 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,38 +58,38 @@ with an incompatible build of this package.
58
58
 
59
59
  ## Features: elements
60
60
 
61
- | Tag | Attributes / properties | Events (all bubble, composed) |
62
- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
63
- | `office-ui-button` | `label`, `icon`, `command`, `disabled`, `pressed`, `expanded`, `icon-only`, `variant="stacked"`, `keyshortcuts` | `office-command` `{ command }` |
64
- | `office-ui-checkbox`, `office-ui-switch` | `checked`, `disabled`, `value`, `aria-label`; form-associated | `input`, `change` |
65
- | `office-ui-select` | `options` `[{ value, label, disabled? }]`, `value`, `selectedIndex`, `disabled` | `input`, `change` |
66
- | `office-ui-ribbon-group` | `label`, `launcher` (dialog launcher command), `launcher-disabled`; default slot | launcher: `office-command` `{ command }` |
67
- | `office-ui-ribbon-stack` | `orientation="horizontal"`; default slot (three-row column of small commands by default) | none |
68
- | `office-ui-menu-button` / `-menu-item` | button: `label`, `icon`, `command` (split button), `variant`, `disabled`, `keyshortcuts`; item: `command`, `label`, `icon`, `disabled`, `checked` | item choice: `office-command` `{ command }` |
69
- | `office-ui-toolbar` | `aria-label`, `aria-orientation`; arrow-key focus movement | none |
70
- | `office-ui-dialog` | `open`, `heading`, `dismissible="false"`, `show()`, `close()`; slots default, `footer` | `office-dialog-close` `{ reason }` (cancelable) |
71
- | `office-ui-status-bar` / `-status-item` | `label`, `value`, `interactive`, `id` | `office-status-activate` `{ id }` |
72
- | `office-ui-zoom-slider` | `value` (percent), `min`, `max`, `step`, `disabled`, `fit` (fit button name), `label` | `input`, `change`; fit: `office-command` `{ command: 'zoom-fit' }` |
73
- | `office-ui-tab-strip` | `tabs` `[{ id, label, title? }]`, `selected`, `label`, `previous-label`, `next-label`, `add-label`, `add-disabled`, `add-title`, `disabled` | `office-tab-select` `{ id }` (cancelable); add: `office-command` `{ command: 'tab-add' }` |
74
- | `office-ui-context-menu` / `office-ui-menu-separator` | `label`; `openAt(x, y)`, `close()`, `open`; `office-ui-menu-item` and separator children | item choice: `office-command` `{ command }` |
75
- | `office-ui-command-search` | `commands` `[{ id, label, keywords?, description?, disabled?, title? }]`, `placeholder`, `label`, `limit` (Office's "Tell me what you want to do") | choice: `office-command` `{ command }` |
76
- | `office-ui-options-dialog` | `categories` `[{ id, label, description?, disabled?, sections: [{ id, title, controls }] }]` (controls: `toggle`, `select`, `number`, `text`, each optionally `disabled` with a reason), `values`, `category`, `open`, `heading`, `show()`, `close()` | OK: `office-options-change` `{ values, changed }` |
77
- | `office-ui-account` | `profile` `{ displayName, avatarColor, initial? }`; slots `sign-in` and default (product sections) | `office-profile-change` `{ profile }` |
78
- | `office-ui-ribbon` | Tab row over tab panels. Panels are children with `data-ribbon-tab`, `data-label`, optional `data-tab-keytip`; slots `quick-access`, `search`, `end`; `selected`, `label`, `file-label`, `no-file`, `file-expanded`, `file-keytip`; `focusFile()`, `focusTab()` | `office-ribbon-select` `{ tab }` (cancelable), `office-ribbon-file` |
79
- | `office-ui-backstage` | `items` `[{ id, label, group?: 'footer', disabled?, title? }]`, `open`, `selected`, `label`, `back-label`, `show(page?)`, `close()`; pages are children with `data-backstage-page` | `office-backstage-select` `{ id }` (cancelable), `office-backstage-close` `{ reason }` (cancelable) |
80
- | `office-ui-find-bar` | `open`, `label`, `input-label`, `placeholder`, `maxlength`, `disabled`, `navigation-disabled`, `previous-label`, `next-label`, `close-label`; `value`, `status`, `statusTitle`, `show()`, `close()` | `office-find-input` `{ query }`, `office-find-step` `{ direction }`, `office-find-close` |
81
- | `office-ui-ruler` | `orientation="vertical"`, `origin` (px offset of zero), `scale` (px per unit, default 96), `direction="reverse"`; decorative | none |
82
- | `office-ui-print-preview` | `pages` (rendered nodes; an inert clone of the current one is shown), `index`, `label`, `empty-label` | `office-print-preview-page` `{ index }` |
83
- | `office-ui-radio` | `checked`, `disabled`, `value`, `name`; radios of one tag sharing a `name` form a roving-focus group; form-associated | `input`, `change` |
84
- | `office-ui-search` | `value`, `placeholder`, `disabled`, `aria-label`, `variant="titlebar"`; `focus()`, `select()` | `input`, `change` |
85
- | `office-ui-ribbon-toggle` | `label`, `checked`, `disabled`, `title`, `command` (a labelled ribbon checkbox row) | `office-toggle` `{ command, checked }` |
86
- | `office-ui-dialog-footer` | `state` `{ actions: [{ id, label, variant?, icon?, disabled?, busy?, title?, align?, testId? }] }`, `focusAction(id)` | `office-dialog-footer-request` `{ id }` |
87
- | `office-ui-toasts` | `state` `{ toasts: [{ id, code?, severity, message }], overflowCount?, labels? }`; hidden when empty, host positions it | `office-toasts-request` `{ id: 'dismiss', toastId }` or `{ id: 'dismissAll' }` |
88
- | `office-ui-read-only-banner` | `state` `{ kind, message, passwordPromptOpen?, passwordError?, checkingPassword?, labels? }` | `office-read-only-request` (Edit anyway, Dismiss, password submit or cancel) |
89
- | `office-ui-paste-options` | `state` `{ left, top, options: [{ id, label }], label? }`; fixed 4px past the pasted object | `office-paste-options-request` `{ format }`, `office-paste-options-dismiss` |
90
- | `office-ui-presence` | `participants`, `max`, `label` | `office-presence-select` `{ id }` |
91
- | `office-ui-smartart` | `drawing` (core `DiagramDrawing`), `schemeColors`, `label` | `office-smartart-render` (render report) |
92
- | `office-ui-icon` | `name`, `label` | none |
61
+ | Tag | Attributes / properties | Events (all bubble, composed) |
62
+ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
63
+ | `office-ui-button` | `label`, `icon`, `command`, `disabled`, `pressed`, `expanded`, `icon-only`, `variant="stacked"`, `keyshortcuts` | `office-command` `{ command }` |
64
+ | `office-ui-checkbox`, `office-ui-switch` | `checked`, `disabled`, `value`, `aria-label`; form-associated | `input`, `change` |
65
+ | `office-ui-select` | `<option>` / `<optgroup>` children (`data-display-label`, `data-description`, font previews) or the `options` property `[{ value, label, disabled? }]`; `value`, `selectedIndex`, `disabled`, `aria-label`, `variant="ribbon-font" | "ribbon-icon"`, `data-font-picker`; `icon`and`custom` slots; arrows, Home, End, PageUp/PageDown, typeahead | `input`, `change` |
66
+ | `office-ui-ribbon-group` | `label`, `launcher` (dialog launcher command), `launcher-disabled`; default slot | launcher: `office-command` `{ command }` |
67
+ | `office-ui-ribbon-stack` | `orientation="horizontal"`; default slot (three-row column of small commands by default) | none |
68
+ | `office-ui-menu-button` / `-menu-item` | button: `label`, `icon`, `command` (split button), `variant`, `disabled`, `keyshortcuts`; item: `command`, `label`, `icon`, `disabled`, `checked` | item choice: `office-command` `{ command }` |
69
+ | `office-ui-toolbar` | `aria-label`, `aria-orientation`; arrow-key focus movement | none |
70
+ | `office-ui-dialog` | `open`, `heading`, `dismissible="false"`, `show()`, `close()`; slots default, `footer` | `office-dialog-close` `{ reason }` (cancelable) |
71
+ | `office-ui-status-bar` / `-status-item` | `label`, `value`, `interactive`, `id` | `office-status-activate` `{ id }` |
72
+ | `office-ui-zoom-slider` | `value` (percent), `min`, `max`, `step`, `disabled`, `fit` (fit button name), `label` | `input`, `change`; fit: `office-command` `{ command: 'zoom-fit' }` |
73
+ | `office-ui-tab-strip` | `tabs` `[{ id, label, title? }]`, `selected`, `label`, `previous-label`, `next-label`, `add-label`, `add-disabled`, `add-title`, `disabled` | `office-tab-select` `{ id }` (cancelable); add: `office-command` `{ command: 'tab-add' }` |
74
+ | `office-ui-context-menu` / `office-ui-menu-separator` | `label`; `openAt(x, y)`, `close()`, `open`; `office-ui-menu-item` and separator children | item choice: `office-command` `{ command }` |
75
+ | `office-ui-command-search` | `commands` `[{ id, label, keywords?, description?, disabled?, title? }]`, `placeholder`, `label`, `limit` (Office's "Tell me what you want to do") | choice: `office-command` `{ command }` |
76
+ | `office-ui-options-dialog` | `categories` `[{ id, label, description?, disabled?, sections: [{ id, title, controls }] }]` (controls: `toggle`, `select`, `number`, `text`, each optionally `disabled` with a reason), `values`, `category`, `open`, `heading`, `show()`, `close()` | OK: `office-options-change` `{ values, changed }` |
77
+ | `office-ui-account` | `profile` `{ displayName, avatarColor, initial? }`; slots `sign-in` and default (product sections) | `office-profile-change` `{ profile }` |
78
+ | `office-ui-ribbon` | Tab row over tab panels. Panels are children with `data-ribbon-tab`, `data-label`, optional `data-tab-keytip`; slots `quick-access`, `search`, `end`; `selected`, `label`, `file-label`, `no-file`, `file-expanded`, `file-keytip`; `focusFile()`, `focusTab()` | `office-ribbon-select` `{ tab }` (cancelable), `office-ribbon-file` |
79
+ | `office-ui-backstage` | `items` `[{ id, label, group?: 'footer', disabled?, title? }]`, `open`, `selected`, `label`, `back-label`, `show(page?)`, `close()`; pages are children with `data-backstage-page` | `office-backstage-select` `{ id }` (cancelable), `office-backstage-close` `{ reason }` (cancelable) |
80
+ | `office-ui-find-bar` | `open`, `label`, `input-label`, `placeholder`, `maxlength`, `disabled`, `navigation-disabled`, `previous-label`, `next-label`, `close-label`; `value`, `status`, `statusTitle`, `show()`, `close()` | `office-find-input` `{ query }`, `office-find-step` `{ direction }`, `office-find-close` |
81
+ | `office-ui-ruler` | `orientation="vertical"`, `origin` (px offset of zero), `scale` (px per unit, default 96), `direction="reverse"`; decorative | none |
82
+ | `office-ui-print-preview` | `pages` (rendered nodes; an inert clone of the current one is shown), `index`, `label`, `empty-label` | `office-print-preview-page` `{ index }` |
83
+ | `office-ui-radio` | `checked`, `disabled`, `value`, `name`; radios of one tag sharing a `name` form a roving-focus group; form-associated | `input`, `change` |
84
+ | `office-ui-search` | `value`, `placeholder`, `disabled`, `aria-label`, `variant="titlebar"`; `focus()`, `select()` | `input`, `change` |
85
+ | `office-ui-ribbon-toggle` | `label`, `checked`, `disabled`, `title`, `command` (a labelled ribbon checkbox row) | `office-toggle` `{ command, checked }` |
86
+ | `office-ui-dialog-footer` | `state` `{ actions: [{ id, label, variant?, icon?, disabled?, busy?, title?, align?, testId? }] }`, `focusAction(id)` | `office-dialog-footer-request` `{ id }` |
87
+ | `office-ui-toasts` | `state` `{ toasts: [{ id, code?, severity, message }], overflowCount?, labels? }`; hidden when empty, host positions it | `office-toasts-request` `{ id: 'dismiss', toastId }` or `{ id: 'dismissAll' }` |
88
+ | `office-ui-read-only-banner` | `state` `{ kind, message, passwordPromptOpen?, passwordError?, checkingPassword?, labels? }` | `office-read-only-request` (Edit anyway, Dismiss, password submit or cancel) |
89
+ | `office-ui-paste-options` | `state` `{ left, top, options: [{ id, label }], label? }`; fixed 4px past the pasted object | `office-paste-options-request` `{ format }`, `office-paste-options-dismiss` |
90
+ | `office-ui-presence` | `participants`, `max`, `label` | `office-presence-select` `{ id }` |
91
+ | `office-ui-smartart` | `drawing` (core `DiagramDrawing`), `schemeColors`, `label` | `office-smartart-render` (render report) |
92
+ | `office-ui-icon` | `name`, `label` | none |
93
93
 
94
94
  The local profile behind `office-ui-account` is stored on the device only, under one suite-wide
95
95
  key (`readOfficeProfile`, `writeOfficeProfile`, `clearOfficeProfile`), so every Office viewer on
@@ -1,4 +1,4 @@
1
- import { TOUCH, tok } from './chunk-MDW2Y5UN.js';
1
+ import { TOUCH, tok } from './chunk-BDJW2KDI.js';
2
2
 
3
3
  // src/registry.ts
4
4
  var CONTRACT_REVISION = 1;
@@ -1,5 +1,5 @@
1
- import { definer, attachStyles, controlCss, emit } from './chunk-D6EREKYF.js';
2
- import { tok } from './chunk-MDW2Y5UN.js';
1
+ import { definer, attachStyles, controlCss, emit } from './chunk-3BHP4IFP.js';
2
+ import { tok } from './chunk-BDJW2KDI.js';
3
3
  import { normalizeHexColor } from 'ooxml-core/color';
4
4
 
5
5
  var SVG_NS = "http://www.w3.org/2000/svg";
@@ -1,4 +1,4 @@
1
- import { OFFICE_TOKENS, TOUCH } from './chunk-MDW2Y5UN.js';
1
+ import { OFFICE_TOKENS, TOUCH } from './chunk-BDJW2KDI.js';
2
2
 
3
3
  // src/theme.ts
4
4
  var THEME_STYLE_ID = "office-ui-theme";
@@ -167,6 +167,15 @@ var OFFICE_TOKENS = {
167
167
  "--office-search-popup-min-width": "240px",
168
168
  "--office-search-popup-max-height": "320px",
169
169
  "--office-select-max-height": "240px",
170
+ "--office-select-background": "var(--office-background, #ffffff)",
171
+ "--office-select-menu-min-width": "90px",
172
+ "--office-select-option-active": "color-mix(in srgb, var(--office-accent, #2563eb) 20%, var(--office-popover, #ffffff))",
173
+ "--office-select-ribbon-menu-width": "192px",
174
+ "--office-font-picker-max-height": "320px",
175
+ "--office-font-picker-width": "256px",
176
+ "--office-chevron-size": "6px",
177
+ "--office-ribbon-combo-height": "28px",
178
+ "--office-ribbon-icon-select-width": "36px",
170
179
  "--office-search-width": "320px",
171
180
  "--office-print-preview-width": "420px",
172
181
  "--office-find-max-width": "640px",
@@ -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-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';
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")};
@@ -3167,199 +3167,497 @@ var defineToolbar = definer("office-ui-toolbar", () => {
3167
3167
  return OfficeUiToolbar;
3168
3168
  });
3169
3169
 
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; }
3170
+ // src/select-menu.ts
3171
+ var SELECT_OPTION_ATTRIBUTES = [
3172
+ "value",
3173
+ "disabled",
3174
+ "label",
3175
+ "selected",
3176
+ "hidden",
3177
+ "data-display-label",
3178
+ "data-description",
3179
+ "style"
3180
+ ];
3181
+ function collectSelectChoices(options) {
3182
+ return options.map((option) => {
3183
+ const parent = option.parentElement;
3184
+ const group = parent?.localName === "optgroup" ? parent : null;
3185
+ return {
3186
+ value: option.value,
3187
+ label: option.label || option.textContent?.trim() || "",
3188
+ disabled: option.disabled || Boolean(group?.disabled),
3189
+ hidden: Boolean(option.hidden) || Boolean(group?.hidden),
3190
+ group: group?.label ?? "",
3191
+ ...option.dataset.displayLabel ? { displayLabel: option.dataset.displayLabel } : {},
3192
+ ...option.dataset.description ? { description: option.dataset.description } : {},
3193
+ ...option.style.fontFamily ? { fontFamily: option.style.fontFamily } : {}
3194
+ };
3195
+ });
3196
+ }
3197
+ function renderSelectMenu(menu, choices, value) {
3198
+ const doc = menu.ownerDocument;
3199
+ const items = [];
3200
+ let previousGroup = "";
3201
+ choices.forEach((choice, index) => {
3202
+ if (choice.hidden) return;
3203
+ if (choice.group && choice.group !== previousGroup) {
3204
+ const heading = doc.createElement("div");
3205
+ heading.className = "group";
3206
+ heading.setAttribute("role", "presentation");
3207
+ heading.textContent = choice.group;
3208
+ items.push(heading);
3209
+ }
3210
+ previousGroup = choice.group;
3211
+ const item = doc.createElement("div");
3212
+ item.className = "option";
3213
+ item.setAttribute("role", "option");
3214
+ item.setAttribute("aria-selected", String(choice.value === value));
3215
+ item.setAttribute("aria-disabled", String(choice.disabled));
3216
+ item.id = `${menu.id}-${index}`;
3217
+ item.dataset.index = String(index);
3218
+ item.textContent = choice.displayLabel ?? choice.label;
3219
+ if (choice.fontFamily) item.style.fontFamily = choice.fontFamily;
3220
+ if (choice.description) {
3221
+ const description = doc.createElement("span");
3222
+ description.className = "description";
3223
+ description.textContent = choice.description;
3224
+ item.append(description);
3225
+ }
3226
+ items.push(item);
3227
+ });
3228
+ menu.replaceChildren(...items);
3229
+ }
3230
+ function tokenPx(el, name, fallback) {
3231
+ const value = el.ownerDocument.defaultView?.getComputedStyle(el).getPropertyValue(name).trim();
3232
+ const px = value?.endsWith("px") ? parseFloat(value) : NaN;
3233
+ return Number.isFinite(px) ? px : fallback;
3234
+ }
3235
+ function positionSelectMenu(menu, trigger, host) {
3236
+ const view = menu.ownerDocument.defaultView;
3237
+ const rect = trigger.getBoundingClientRect();
3238
+ const margin = tokenPx(host, "--office-space-2", 8);
3239
+ const gap = tokenPx(host, "--office-space-1", 4);
3240
+ const width = Math.max(0, view.innerWidth - margin * 2);
3241
+ const below = Math.max(0, view.innerHeight - rect.bottom - gap - margin);
3242
+ const above = Math.max(0, rect.top - gap - margin);
3243
+ menu.style.minWidth = `${Math.min(rect.width, width)}px`;
3244
+ 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);
3246
+ menu.style.maxHeight = `${cap}px`;
3247
+ const preferred = menu.getBoundingClientRect().height;
3248
+ const flip = below < preferred && above > below;
3249
+ menu.style.maxHeight = `${Math.min(cap, flip ? above : below)}px`;
3250
+ const { height, width: menuWidth } = menu.getBoundingClientRect();
3251
+ menu.style.top = `${Math.max(margin, flip ? rect.top - gap - height : rect.bottom + gap)}px`;
3252
+ menu.style.left = `${Math.max(margin, Math.min(rect.left, view.innerWidth - menuWidth - margin))}px`;
3253
+ }
3254
+ function markSelectActive(menu, trigger, active, open) {
3255
+ for (const item of menu.querySelectorAll("[data-index]"))
3256
+ item.toggleAttribute("data-active", Number(item.dataset.index) === active && open);
3257
+ if (open && active >= 0) trigger.setAttribute("aria-activedescendant", `${menu.id}-${active}`);
3258
+ menu.querySelector("[data-active]")?.scrollIntoView?.({ block: "nearest" });
3259
+ }
3260
+ function nextSelectActive(choices, active, step) {
3261
+ for (let i = 0; i < choices.length; i++) {
3262
+ active = (active + step + choices.length) % choices.length;
3263
+ if (!choices[active].disabled && !choices[active].hidden) return active;
3264
+ }
3265
+ return active;
3266
+ }
3267
+ var SELECT_PAGE_SIZE = 8;
3268
+ function pageSelectActive(choices, active, direction) {
3269
+ const usable = (index) => !choices[index]?.disabled && !choices[index]?.hidden;
3270
+ const last = choices.length - 1;
3271
+ const target = Math.max(0, Math.min(last, Math.max(active, 0) + direction * SELECT_PAGE_SIZE));
3272
+ for (let index = target; index >= 0 && index <= last; index -= direction)
3273
+ if (usable(index)) return index;
3274
+ return active;
3275
+ }
3276
+ function createSelectTrigger(doc) {
3277
+ const trigger = doc.createElement("button");
3278
+ trigger.type = "button";
3279
+ trigger.setAttribute("part", "trigger");
3280
+ trigger.setAttribute("role", "combobox");
3281
+ trigger.setAttribute("aria-haspopup", "listbox");
3282
+ trigger.setAttribute("aria-expanded", "false");
3283
+ const icon2 = doc.createElement("slot");
3284
+ icon2.name = "icon";
3285
+ const text2 = doc.createElement("span");
3286
+ text2.className = "value";
3287
+ text2.setAttribute("part", "value");
3288
+ const chevron = doc.createElement("span");
3289
+ chevron.className = "chevron";
3290
+ chevron.setAttribute("part", "indicator");
3291
+ chevron.setAttribute("aria-hidden", "true");
3292
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
3293
+ svg.setAttribute("viewBox", "0 0 24 24");
3294
+ svg.setAttribute("fill", "none");
3295
+ svg.setAttribute("stroke", "currentColor");
3296
+ svg.setAttribute("stroke-width", "2");
3297
+ svg.setAttribute("stroke-linecap", "round");
3298
+ svg.setAttribute("stroke-linejoin", "round");
3299
+ const path = doc.createElementNS(svg.namespaceURI, "path");
3300
+ path.setAttribute("d", "m6 9 6 6 6-6");
3301
+ svg.append(path);
3302
+ chevron.append(svg);
3303
+ trigger.append(icon2, text2, chevron);
3304
+ return { trigger, text: text2 };
3305
+ }
3306
+ function prependSelectCustomSlot(host, menu) {
3307
+ if (!host.querySelector('[slot="custom"]')) return;
3308
+ const custom = host.ownerDocument.createElement("slot");
3309
+ custom.name = "custom";
3310
+ menu.prepend(custom);
3311
+ }
3312
+
3313
+ // src/select-styles.ts
3314
+ var SELECT_CSS = `
3315
+ :host { display: inline-block; box-sizing: border-box; min-width: 0; max-width: 100%;
3316
+ color: ${tok("--office-foreground")}; font: inherit; font-size: ${tok("--office-font-size-sm")}; vertical-align: middle; }
3317
+ :host([disabled]) { opacity: .5; cursor: not-allowed; }
3318
+ button { box-sizing: border-box; width: 100%; min-height: ${tok("--office-field-height")}; display: flex; align-items: center;
3319
+ justify-content: space-between; gap: ${tok("--office-space-2-5")}; padding: ${tok("--office-space-1")} ${tok("--office-space-1-5")};
3320
+ border: ${tok("--office-border-width")} solid ${tok("--office-field-border")}; border-radius: ${tok("--office-field-radius")};
3321
+ background: ${tok("--office-select-background")}; color: inherit; font: inherit; font-size: inherit; text-align: start; cursor: pointer; }
3322
+ button:focus-visible { ${FOCUS_RING} }
3323
+ button:disabled { cursor: not-allowed; }
3324
+ .value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3325
+ .chevron svg { display: none; }
3326
+ .chevron { flex: none; width: ${tok("--office-chevron-size")}; height: ${tok("--office-chevron-size")};
3327
+ margin: 0 ${tok("--office-space-0")} ${tok("--office-space-0")} 0;
3328
+ border-right: calc(${tok("--office-border-width")} * 1.5) solid currentColor;
3329
+ border-bottom: calc(${tok("--office-border-width")} * 1.5) solid currentColor; transform: rotate(45deg); }
3330
+ :host([open]) .chevron { margin: ${tok("--office-space-0")} ${tok("--office-space-0")} 0 0; transform: rotate(225deg); }
3331
+ .menu { box-sizing: border-box; position: fixed; margin: 0; padding: ${tok("--office-space-1")};
3332
+ min-width: ${tok("--office-select-menu-min-width")}; max-height: min(${tok("--office-select-max-height")}, 50vh); overflow: auto;
3333
+ border: ${tok("--office-border-width")} solid ${tok("--office-field-border")}; border-radius: ${tok("--office-field-radius")};
3334
+ background: ${tok("--office-popover")}; color: ${tok("--office-popover-foreground")}; box-shadow: ${tok("--office-shadow")};
3335
+ font: inherit; font-size: inherit; }
3336
+ .menu:not(:popover-open):not([data-fallback-open]) { display: none; }
3337
+ .option { display: block; box-sizing: border-box; width: 100%; padding: ${tok("--office-space-1-5")} ${tok("--office-space-2")};
3338
+ border-radius: ${tok("--office-radius-sm")}; cursor: pointer; white-space: nowrap; }
3339
+ .group { padding: ${tok("--office-space-1-5")} ${tok("--office-space-2")} ${tok("--office-space-0")};
3340
+ color: ${tok("--office-muted-foreground")}; font-size: .9em; font-weight: ${tok("--office-font-weight-bold")}; }
3341
+ .option:not([aria-disabled="true"]):hover, .option[data-active]:not([aria-selected="true"]) {
3342
+ background: ${tok("--office-select-option-active")}; }
3343
+ .option[aria-selected="true"] { background: ${tok("--office-accent")}; color: ${tok("--office-accent-foreground")}; }
3344
+ .option[aria-selected="true"]:hover { filter: brightness(1.08); }
3345
+ .option[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
3346
+ .description { flex: none; color: ${tok("--office-muted-foreground")}; font-size: ${tok("--office-font-size-2xs")}; }
3347
+
3348
+ :host([variant^="ribbon-"][disabled]) { opacity: .4; }
3349
+ :host([variant^="ribbon-"]) button { height: ${tok("--office-ribbon-combo-height")}; min-height: ${tok("--office-ribbon-combo-height")};
3350
+ padding: ${tok("--office-space-1")} ${tok("--office-space-2")}; gap: ${tok("--office-space-1")};
3351
+ border: ${tok("--office-border-width")} solid color-mix(in oklab, ${tok("--office-border")} 60%, transparent);
3352
+ border-radius: ${tok("--office-radius-sm")}; font-size: ${tok("--office-font-size-sm")};
3353
+ background: color-mix(in oklab, ${tok("--office-background")} 60%, transparent); color: ${tok("--office-foreground")}; }
3354
+ :host([variant^="ribbon-"]) .value { flex: 1; min-width: 0; }
3355
+ :host([variant^="ribbon-"]) .chevron { width: ${tok("--office-icon-size-md")}; height: ${tok("--office-icon-size-md")}; margin: 0; border: 0; transform: none; }
3356
+ :host([variant^="ribbon-"]) .chevron svg { display: block; width: ${tok("--office-icon-size-md")}; height: ${tok("--office-icon-size-md")}; }
3357
+ :host([variant="ribbon-font"]) .chevron { color: ${tok("--office-muted-foreground")}; }
3358
+ :host([variant="ribbon-font"][data-font-picker="size"]) button { padding-inline: ${tok("--office-space-1-5")}; }
3359
+ :host([variant="ribbon-icon"]) button { width: ${tok("--office-ribbon-icon-select-width")}; gap: ${tok("--office-space-1-5")};
3360
+ padding: ${tok("--office-space-1-5")} ${tok("--office-space-2-5")}; border: 0; background: ${tok("--office-surface")}; justify-content: center; }
3361
+ :host([variant="ribbon-icon"]) :is(.value, .chevron) { display: none; }
3362
+ ::slotted(svg[slot="icon"]) { display: block; width: ${tok("--office-icon-size-md")}; height: ${tok("--office-icon-size-md")}; flex: none; }
3363
+ :host([variant^="ribbon-"]) button:hover:not(:disabled) { background: ${tok("--office-selected")}; }
3364
+ :host([variant^="ribbon-"]) .menu { width: ${tok("--office-select-ribbon-menu-width")}; padding: ${tok("--office-space-1")} 0;
3365
+ border-radius: ${tok("--office-radius-lg")}; font-size: ${tok("--office-font-size-sm")}; }
3366
+ :host([data-font-picker="family"]) .menu { width: ${tok("--office-font-picker-width")}; }
3367
+ :host([variant^="ribbon-"]) .option { display: flex; justify-content: space-between; gap: ${tok("--office-space-3")};
3368
+ padding: ${tok("--office-space-1-5")} ${tok("--office-space-3")}; border-radius: 0; }
3369
+ :host([variant^="ribbon-"]) .option[aria-selected="true"] { background: transparent; color: ${tok("--office-foreground")}; }
3370
+ :host([variant^="ribbon-"]) .option:hover, :host([variant^="ribbon-"]) .option[data-active] { background: ${tok("--office-surface")}; filter: none; }
3371
+ :host([variant^="ribbon-"]) .group { padding: ${tok("--office-space-2")} ${tok("--office-space-3")} ${tok("--office-space-1")};
3372
+ text-transform: uppercase; letter-spacing: .02em; font-size: ${tok("--office-font-size-2xs")}; }
3373
+ @media (pointer: coarse), (max-width: 767px) {
3374
+ button, :host([variant^="ribbon-"]) button { min-height: ${tok("--office-target-size-touch")}; }
3375
+ :host([variant^="ribbon-"]) button { height: ${tok("--office-target-size-touch")}; }
3376
+ }
3188
3377
  @media (forced-colors: active) {
3189
- button, ul { border-color: CanvasText; background: Canvas; color: CanvasText; }
3190
- li[data-active] { background: Highlight; color: HighlightText; }
3378
+ button, .menu { border-color: CanvasText; background: Canvas; color: CanvasText; }
3379
+ .option[aria-selected="true"], .option[data-active], .option:not([aria-disabled="true"]):hover {
3380
+ background: Highlight; color: HighlightText; }
3191
3381
  }
3192
3382
  `;
3383
+
3384
+ // src/select.ts
3193
3385
  var uid2 = 0;
3194
3386
  var defineSelect = definer("office-ui-select", () => {
3195
3387
  class OfficeUiSelect extends HTMLElement {
3388
+ static formAssociated = true;
3196
3389
  static observedAttributes = ["value", "disabled", "aria-label"];
3197
- opts = [];
3198
- current = "";
3199
- active = -1;
3200
- typed = "";
3201
- typedAt = 0;
3390
+ internals;
3202
3391
  trigger;
3203
- list;
3204
- label;
3205
- domId = `office-ui-select-${++uid2}`;
3392
+ text;
3393
+ menu;
3394
+ observer;
3395
+ refreshFrame = 0;
3396
+ data = [];
3397
+ choices = [];
3398
+ active = -1;
3399
+ isOpen = false;
3400
+ search = "";
3401
+ searchTimer = 0;
3402
+ defaultValue = "";
3403
+ menuKey = "";
3206
3404
  constructor() {
3207
3405
  super();
3406
+ try {
3407
+ this.internals = this.attachInternals();
3408
+ } catch {
3409
+ this.internals = void 0;
3410
+ }
3208
3411
  const doc = this.ownerDocument;
3209
3412
  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));
3413
+ attachStyles(root, controlCss(SELECT_CSS));
3414
+ ({ trigger: this.trigger, text: this.text } = createSelectTrigger(doc));
3415
+ this.menu = doc.createElement("div");
3416
+ this.menu.className = "menu";
3417
+ this.menu.id = `office-ui-select-options-${++uid2}`;
3418
+ this.menu.setAttribute("role", "listbox");
3419
+ this.menu.setAttribute("popover", "manual");
3420
+ this.menu.setAttribute("part", "popup");
3421
+ this.trigger.setAttribute("aria-controls", this.menu.id);
3422
+ root.append(this.trigger, this.menu);
3423
+ this.trigger.addEventListener("click", () => this.isOpen ? this.close() : this.show());
3424
+ this.trigger.addEventListener("keydown", (event) => this.onKeyDown(event));
3425
+ this.menu.addEventListener("pointerdown", (event) => {
3426
+ if (event.target.closest("[data-index]")) event.preventDefault();
3427
+ });
3428
+ this.menu.addEventListener("click", (event) => {
3429
+ const target = event.target.closest("[data-index]");
3430
+ if (target) this.commit(Number(target.dataset.index));
3234
3431
  });
3432
+ const Observer = doc.defaultView?.MutationObserver;
3433
+ this.observer = Observer ? new Observer(() => {
3434
+ const view = this.ownerDocument.defaultView;
3435
+ if (this.refreshFrame || !view) return;
3436
+ this.refreshFrame = view.requestAnimationFrame(() => {
3437
+ this.refreshFrame = 0;
3438
+ this.refresh();
3439
+ });
3440
+ }) : void 0;
3235
3441
  }
3236
3442
  connectedCallback() {
3237
- this.sync();
3443
+ this.defaultValue = this.getAttribute("value") ?? "";
3444
+ this.observer?.observe(this, {
3445
+ childList: true,
3446
+ subtree: true,
3447
+ attributes: true,
3448
+ characterData: true,
3449
+ attributeFilter: SELECT_OPTION_ATTRIBUTES
3450
+ });
3451
+ this.refresh();
3238
3452
  }
3239
- attributeChangedCallback(name, _old, value) {
3240
- if (name === "value") this.current = value ?? "";
3241
- this.sync();
3453
+ disconnectedCallback() {
3454
+ this.close();
3455
+ const view = this.ownerDocument.defaultView;
3456
+ view?.clearTimeout(this.searchTimer);
3457
+ this.searchTimer = 0;
3458
+ this.search = "";
3459
+ this.observer?.disconnect();
3460
+ if (this.refreshFrame) view?.cancelAnimationFrame(this.refreshFrame);
3461
+ this.refreshFrame = 0;
3462
+ }
3463
+ attributeChangedCallback() {
3464
+ this.refresh();
3242
3465
  }
3466
+ /** The `<option>` children, or the programmatic options when there are none. */
3243
3467
  get options() {
3244
- return this.opts;
3468
+ const children = [...this.querySelectorAll("option")];
3469
+ return children.length ? children : this.data;
3245
3470
  }
3246
3471
  set options(next) {
3247
- this.opts = next.map((option) => ({ ...option }));
3248
- this.sync();
3472
+ this.data = [...next];
3473
+ this.refresh();
3249
3474
  }
3250
3475
  get value() {
3251
- return this.current;
3476
+ const requested = this.getAttribute("value");
3477
+ const choices = this.choices.length ? this.choices : this.collect();
3478
+ if (requested !== null && this.getAttribute("variant") === "ribbon-font") return requested;
3479
+ if (requested !== null && (!choices.length || choices.some((c) => c.value === requested)))
3480
+ return requested;
3481
+ return [...this.querySelectorAll("option")].find((option) => option.selected)?.value ?? choices.find((choice) => !choice.disabled && !choice.hidden)?.value ?? choices[0]?.value ?? requested ?? "";
3252
3482
  }
3253
3483
  set value(next) {
3254
- this.current = String(next);
3255
- this.sync();
3484
+ this.setAttribute("value", String(next ?? ""));
3256
3485
  }
3257
3486
  get selectedIndex() {
3258
- return this.opts.findIndex((option) => option.value === this.current);
3487
+ return this.options.findIndex((option) => option.value === this.value);
3259
3488
  }
3260
3489
  set selectedIndex(index) {
3261
- const option = this.opts[index];
3262
- if (option) this.value = option.value;
3490
+ this.value = this.options[index]?.value ?? "";
3263
3491
  }
3264
3492
  get disabled() {
3265
3493
  return this.hasAttribute("disabled");
3266
3494
  }
3267
- set disabled(value) {
3268
- this.toggleAttribute("disabled", Boolean(value));
3495
+ set disabled(next) {
3496
+ this.toggleAttribute("disabled", Boolean(next));
3269
3497
  }
3270
- get isOpen() {
3271
- return !this.list.hidden;
3498
+ focus(options) {
3499
+ this.trigger.focus(options);
3272
3500
  }
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));
3501
+ formResetCallback() {
3502
+ this.value = this.defaultValue;
3278
3503
  }
3279
- close() {
3280
- this.list.hidden = true;
3281
- this.trigger.setAttribute("aria-expanded", "false");
3282
- this.trigger.removeAttribute("aria-activedescendant");
3283
- this.active = -1;
3504
+ formDisabledCallback(disabled) {
3505
+ this.disabled = disabled;
3506
+ }
3507
+ collect() {
3508
+ const children = [...this.querySelectorAll("option")];
3509
+ if (children.length) return collectSelectChoices(children);
3510
+ return this.data.map((option) => ({
3511
+ value: option.value,
3512
+ label: option.label,
3513
+ disabled: option.disabled === true,
3514
+ hidden: false,
3515
+ group: ""
3516
+ }));
3284
3517
  }
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;
3518
+ refresh() {
3519
+ if (!this.trigger) return;
3520
+ this.choices = this.collect();
3521
+ if (!this.hasAttribute("value") && this.choices.length) {
3522
+ const children = [...this.querySelectorAll("option")];
3523
+ this.setAttribute(
3524
+ "value",
3525
+ children.find((option) => option.selected)?.value ?? this.choices.find((choice) => !choice.disabled && !choice.hidden)?.value ?? this.choices[0].value
3526
+ );
3527
+ return;
3288
3528
  }
3289
- return from;
3529
+ const shown = this.choices.find((choice) => choice.value === this.value);
3530
+ this.text.textContent = shown?.displayLabel ?? shown?.label ?? this.value;
3531
+ this.trigger.disabled = this.disabled;
3532
+ if (this.disabled) this.close();
3533
+ const name = this.getAttribute("aria-label");
3534
+ if (name) this.trigger.setAttribute("aria-label", name);
3535
+ else this.trigger.removeAttribute("aria-label");
3536
+ this.internals?.setFormValue?.(this.disabled ? null : this.value);
3537
+ if ((this.isOpen || this.menu.childElementCount) && this.menuKey !== this.currentMenuKey())
3538
+ this.renderMenu();
3290
3539
  }
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}`);
3540
+ currentMenuKey() {
3541
+ return `${this.value}|${JSON.stringify(this.choices)}`;
3295
3542
  }
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;
3543
+ renderMenu() {
3544
+ this.menuKey = this.currentMenuKey();
3545
+ renderSelectMenu(this.menu, this.choices, this.value);
3546
+ prependSelectCustomSlot(this, this.menu);
3547
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3548
+ }
3549
+ show() {
3550
+ if (this.disabled || this.isOpen || !this.choices.some((c) => !c.disabled && !c.hidden))
3551
+ return;
3552
+ this.isOpen = true;
3553
+ this.setAttribute("open", "");
3554
+ const selected = this.choices.findIndex(
3555
+ (choice) => choice.value === this.value && !choice.disabled && !choice.hidden
3556
+ );
3557
+ this.active = selected >= 0 ? selected : this.choices.findIndex((choice) => !choice.disabled && !choice.hidden);
3558
+ this.renderMenu();
3559
+ if (typeof this.menu.showPopover === "function") this.menu.showPopover();
3560
+ else this.menu.dataset.fallbackOpen = "";
3561
+ this.trigger.setAttribute("aria-expanded", "true");
3562
+ this.position();
3563
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3564
+ const view = this.ownerDocument.defaultView;
3565
+ this.ownerDocument.addEventListener("pointerdown", this.onOutside, true);
3566
+ view?.addEventListener("resize", this.position);
3567
+ view?.addEventListener("scroll", this.position, true);
3568
+ }
3569
+ close() {
3570
+ if (!this.isOpen) return;
3571
+ this.isOpen = false;
3572
+ const view = this.ownerDocument.defaultView;
3573
+ view?.clearTimeout(this.searchTimer);
3574
+ this.search = "";
3575
+ this.removeAttribute("open");
3576
+ try {
3577
+ if (this.menu.matches(":popover-open")) this.menu.hidePopover();
3578
+ } catch {
3579
+ }
3580
+ delete this.menu.dataset.fallbackOpen;
3581
+ this.trigger.setAttribute("aria-expanded", "false");
3582
+ this.trigger.removeAttribute("aria-activedescendant");
3583
+ this.ownerDocument.removeEventListener("pointerdown", this.onOutside, true);
3584
+ view?.removeEventListener("resize", this.position);
3585
+ view?.removeEventListener("scroll", this.position, true);
3586
+ }
3587
+ onOutside = (event) => {
3588
+ if (!event.composedPath().includes(this)) this.close();
3589
+ };
3590
+ position = () => {
3591
+ if (this.isOpen) positionSelectMenu(this.menu, this.trigger, this);
3592
+ };
3593
+ move(step) {
3594
+ if (!this.isOpen) this.show();
3595
+ if (!this.isOpen) return;
3596
+ this.active = nextSelectActive(this.choices, this.active, step);
3597
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3598
+ }
3599
+ commit(index) {
3600
+ const choice = this.choices[index];
3601
+ if (!choice || choice.disabled || choice.hidden) return;
3602
+ const changed = this.value !== choice.value;
3603
+ this.value = choice.value;
3301
3604
  this.close();
3605
+ this.trigger.focus();
3302
3606
  if (changed) {
3303
3607
  this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
3304
3608
  this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
3305
3609
  }
3306
3610
  }
3307
- onKey(event) {
3611
+ onKeyDown(event) {
3308
3612
  if (this.disabled) return;
3309
- const key = event.key;
3310
- if (key === "ArrowDown" || key === "ArrowUp") {
3613
+ const stop = () => {
3311
3614
  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 === " ") {
3319
- 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)
3615
+ event.stopPropagation();
3616
+ };
3617
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
3618
+ stop();
3619
+ this.move(event.key === "ArrowDown" ? 1 : -1);
3620
+ } else if (event.key === "Home" || event.key === "End") {
3621
+ stop();
3622
+ this.show();
3623
+ this.active = event.key === "Home" ? nextSelectActive(this.choices, -1, 1) : nextSelectActive(this.choices, 0, -1);
3624
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3625
+ } else if (event.key === "PageUp" || event.key === "PageDown") {
3626
+ stop();
3627
+ this.show();
3628
+ this.active = pageSelectActive(
3629
+ this.choices,
3630
+ this.active,
3631
+ event.key === "PageDown" ? 1 : -1
3632
+ );
3633
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3634
+ } else if (event.key === "Enter" || event.key === " ") {
3635
+ stop();
3636
+ if (this.isOpen) this.commit(this.active);
3637
+ else this.show();
3638
+ } else if (event.key === "Escape" && this.isOpen) {
3639
+ stop();
3640
+ this.close();
3641
+ this.trigger.focus();
3642
+ } else if (event.key === "Tab") this.close();
3643
+ else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
3644
+ event.stopPropagation();
3645
+ const view = this.ownerDocument.defaultView;
3646
+ this.search += event.key.toLocaleLowerCase();
3647
+ view?.clearTimeout(this.searchTimer);
3648
+ this.searchTimer = view?.setTimeout(() => {
3649
+ this.search = "";
3650
+ }, 700) ?? 0;
3651
+ this.show();
3652
+ const index = this.choices.findIndex(
3653
+ (choice) => !choice.disabled && !choice.hidden && choice.label.toLocaleLowerCase().startsWith(this.search)
3335
3654
  );
3336
3655
  if (index >= 0) {
3337
- if (this.isOpen) this.setActive(index);
3338
- else this.choose(index);
3656
+ this.active = index;
3657
+ markSelectActive(this.menu, this.trigger, this.active, this.isOpen);
3339
3658
  }
3340
3659
  }
3341
3660
  }
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
3661
  }
3364
3662
  return OfficeUiSelect;
3365
3663
  });
@@ -3458,7 +3756,7 @@ var defineStatusItem = definer("office-ui-status-item", () => {
3458
3756
  });
3459
3757
 
3460
3758
  // src/tab-strip.ts
3461
- var CSS16 = `
3759
+ var CSS15 = `
3462
3760
  :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
3761
  background: ${tok("--office-surface")}; color: ${tok("--office-muted-foreground")};
3464
3762
  border-top: ${tok("--office-border-width")} solid ${tok("--office-border")}; font-size: ${tok("--office-font-size-sm")};
@@ -3505,7 +3803,7 @@ var defineTabStrip = definer("office-ui-tab-strip", () => {
3505
3803
  super();
3506
3804
  const doc = this.ownerDocument;
3507
3805
  const root = this.attachShadow({ mode: "open" });
3508
- attachStyles(root, controlCss(CSS16));
3806
+ attachStyles(root, controlCss(CSS15));
3509
3807
  const step = (name, icon2) => {
3510
3808
  const button = doc.createElement("button");
3511
3809
  button.type = "button";
@@ -3644,7 +3942,7 @@ var defineTabStrip = definer("office-ui-tab-strip", () => {
3644
3942
  });
3645
3943
 
3646
3944
  // src/zoom-slider.ts
3647
- var CSS17 = `
3945
+ var CSS16 = `
3648
3946
  :host { display: inline-flex; align-items: center; gap: ${tok("--office-space-0")}; font-size: ${tok("--office-font-size-xs")};
3649
3947
  color: ${tok("--office-foreground")}; font-family: ${tok("--office-font")}; }
3650
3948
  button { box-sizing: border-box; display: inline-grid; place-items: center; min-width: ${tok("--office-target-size")};
@@ -3680,7 +3978,7 @@ var defineZoomSlider = definer("office-ui-zoom-slider", () => {
3680
3978
  super();
3681
3979
  const doc = this.ownerDocument;
3682
3980
  const root = this.attachShadow({ mode: "open" });
3683
- attachStyles(root, controlCss(CSS17));
3981
+ attachStyles(root, controlCss(CSS16));
3684
3982
  const button = (name, text2) => {
3685
3983
  const element = doc.createElement("button");
3686
3984
  element.type = "button";
@@ -1,5 +1,5 @@
1
- import { definer, attachStyles, controlCss, emit } from './chunk-D6EREKYF.js';
2
- import { tok } from './chunk-MDW2Y5UN.js';
1
+ import { definer, attachStyles, controlCss, emit } from './chunk-3BHP4IFP.js';
2
+ import { tok } from './chunk-BDJW2KDI.js';
3
3
 
4
4
  // src/presence.ts
5
5
  function participantsFromAwareness(states, selfId) {
@@ -1,4 +1,4 @@
1
- import { definer, attachStyles, controlCss } from './chunk-D6EREKYF.js';
1
+ import { definer, attachStyles, controlCss } from './chunk-3BHP4IFP.js';
2
2
 
3
3
  // src/icons.ts
4
4
  var registry = /* @__PURE__ */ new Map();
package/dist/controls.js CHANGED
@@ -1,5 +1,5 @@
1
- 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 } from './chunk-JROJGF7A.js';
2
- export { defineIcon } from './chunk-4NUKVNSJ.js';
3
- import './chunk-NOQAM6ZS.js';
4
- import './chunk-D6EREKYF.js';
5
- import './chunk-MDW2Y5UN.js';
1
+ 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 } from './chunk-EWNLGZPX.js';
2
+ export { defineIcon } from './chunk-WNSUNQIY.js';
3
+ import './chunk-WKLVWPI7.js';
4
+ import './chunk-3BHP4IFP.js';
5
+ import './chunk-BDJW2KDI.js';
package/dist/icons.js CHANGED
@@ -1,3 +1,3 @@
1
- export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-4NUKVNSJ.js';
2
- import './chunk-D6EREKYF.js';
3
- import './chunk-MDW2Y5UN.js';
1
+ export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-WNSUNQIY.js';
2
+ import './chunk-3BHP4IFP.js';
3
+ import './chunk-BDJW2KDI.js';
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
- import { installOfficeUiTheme } from './chunk-LI6OV55P.js';
2
- export { THEME_CSS, THEME_STYLE_ID, installOfficeUiTheme } from './chunk-LI6OV55P.js';
3
- import { registerControls } from './chunk-JROJGF7A.js';
4
- 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 } from './chunk-JROJGF7A.js';
5
- export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-4NUKVNSJ.js';
6
- import { definePresence } from './chunk-NOQAM6ZS.js';
7
- export { definePresence, initialsOf, participantsFromAwareness } from './chunk-NOQAM6ZS.js';
8
- import { defineSmartArt } from './chunk-SDFQ7U2I.js';
9
- export { defineSmartArt, renderDiagramDrawing } from './chunk-SDFQ7U2I.js';
10
- export { CONTRACT_REVISION } from './chunk-D6EREKYF.js';
11
- export { COMPACT, FOCUS_RING, OFFICE_TOKENS, TOUCH, tok } from './chunk-MDW2Y5UN.js';
1
+ import { installOfficeUiTheme } from './chunk-7CCQN3VM.js';
2
+ export { THEME_CSS, THEME_STYLE_ID, installOfficeUiTheme } from './chunk-7CCQN3VM.js';
3
+ import { registerControls } from './chunk-EWNLGZPX.js';
4
+ 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 } from './chunk-EWNLGZPX.js';
5
+ export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-WNSUNQIY.js';
6
+ import { definePresence } from './chunk-WKLVWPI7.js';
7
+ export { definePresence, initialsOf, participantsFromAwareness } from './chunk-WKLVWPI7.js';
8
+ import { defineSmartArt } from './chunk-4WUYOQRL.js';
9
+ export { defineSmartArt, renderDiagramDrawing } from './chunk-4WUYOQRL.js';
10
+ export { CONTRACT_REVISION } from './chunk-3BHP4IFP.js';
11
+ export { COMPACT, FOCUS_RING, OFFICE_TOKENS, TOUCH, tok } from './chunk-BDJW2KDI.js';
12
12
 
13
13
  // src/index.ts
14
14
  var OFFICE_UI_TAGS = [
package/dist/presence.js CHANGED
@@ -1,3 +1,3 @@
1
- export { definePresence, initialsOf, participantsFromAwareness } from './chunk-NOQAM6ZS.js';
2
- import './chunk-D6EREKYF.js';
3
- import './chunk-MDW2Y5UN.js';
1
+ export { definePresence, initialsOf, participantsFromAwareness } from './chunk-WKLVWPI7.js';
2
+ import './chunk-3BHP4IFP.js';
3
+ import './chunk-BDJW2KDI.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Popup helpers of `office-ui-select`. Moved from pptx-viewer
3
+ * (`packages/shared/src/web-components/select-menu.ts`, `select-trigger.ts`).
4
+ */
5
+ /** Mutations that can change a select's value, labels or font preview. */
6
+ export declare const SELECT_OPTION_ATTRIBUTES: string[];
7
+ /** The flattened option data the popup renders. */
8
+ export interface SelectChoice {
9
+ value: string;
10
+ label: string;
11
+ disabled: boolean;
12
+ hidden: boolean;
13
+ group: string;
14
+ displayLabel?: string;
15
+ description?: string;
16
+ fontFamily?: string;
17
+ }
18
+ /** Flatten `<option>` children, keeping optgroup labels and disabled or hidden state. */
19
+ export declare function collectSelectChoices(options: HTMLOptionElement[]): SelectChoice[];
20
+ /** Build the popup's options (only while it is open or has been opened). */
21
+ export declare function renderSelectMenu(menu: HTMLElement, choices: SelectChoice[], value: string): void;
22
+ /**
23
+ * Prefer below the trigger, flipping above when that offers more room. Margins, the gap and the
24
+ * height cap come from `--office-space-2`, `--office-space-1` and `--office-select-max-height`
25
+ * (`--office-font-picker-max-height` for a font family picker).
26
+ */
27
+ export declare function positionSelectMenu(menu: HTMLElement, trigger: HTMLElement, host: HTMLElement): void;
28
+ /** Expose the keyboard target while keeping the selected state separate. */
29
+ export declare function markSelectActive(menu: HTMLElement, trigger: HTMLElement, active: number, open: boolean): void;
30
+ /** The next enabled, visible option when moving with the arrow keys (wrapping). */
31
+ export declare function nextSelectActive(choices: SelectChoice[], active: number, step: number): number;
32
+ /** Options a PageUp/PageDown press moves, like the visible rows of a native listbox. */
33
+ export declare const SELECT_PAGE_SIZE = 8;
34
+ /** Jump a page of options up (-1) or down (+1), clamped to the nearest enabled option. */
35
+ export declare function pageSelectActive(choices: SelectChoice[], active: number, direction: 1 | -1): number;
36
+ /** Trigger with an `icon` slot, the value text and a chevron. */
37
+ export declare function createSelectTrigger(doc: Document): {
38
+ trigger: HTMLButtonElement;
39
+ text: HTMLSpanElement;
40
+ };
41
+ /** Optional editable content (`slot="custom"`) stays in the light DOM so hosts own its events. */
42
+ export declare function prependSelectCustomSlot(host: HTMLElement, menu: HTMLElement): void;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `office-ui-select` styles, moved from pptx-viewer (`select-styles.ts`, `select-ribbon-styles.ts`)
3
+ * and expressed in tokens. `variant="ribbon-font"` and `variant="ribbon-icon"` are the compact
4
+ * ribbon combo and icon picker; `data-font-picker="family"` widens and lengthens the popup.
5
+ */
6
+ export declare const SELECT_CSS: string;
package/dist/select.d.ts CHANGED
@@ -1,13 +1,19 @@
1
+ /** A programmatic choice; `<option>` elements satisfy the same shape. */
1
2
  export interface OfficeSelectOption {
2
3
  value: string;
3
4
  label: string;
4
5
  disabled?: boolean;
5
6
  }
6
7
  /**
7
- * Single-select dropdown. Properties: `options` (array), `value`, `disabled`, `selectedIndex`;
8
- * attributes `value`, `disabled`, `aria-label`. Trigger has `role="combobox"` and the popup is a
9
- * `listbox`. Keyboard: ArrowDown/Up (open, move), Home, End, Enter/Space (choose), Escape
10
- * (close), printable keys (typeahead); disabled options are skipped. `input` then `change`
11
- * bubble when the user picks; setting a property is silent.
8
+ * Single-select combobox. Moved from pptx-viewer `pptx-ui-select` (the declarative, most complete
9
+ * implementation). Choices come from `<option>` / `<optgroup>` children (with `data-display-label`,
10
+ * `data-description` and font previews through `style="font-family"`), or from the `options`
11
+ * property when there are none. `value`, `disabled`, `selectedIndex`, `aria-label`;
12
+ * `variant="ribbon-font"` (compact ribbon combo that accepts unlisted values, such as a typed font
13
+ * size) or `"ribbon-icon"`; `data-font-picker="family|size"`; an `icon` slot and a `custom` slot at
14
+ * the top of the popup. Keyboard: arrows, Home, End, PageUp/PageDown (8 options), typeahead,
15
+ * Enter/Space, Escape, Tab; disabled and hidden choices are skipped. The popup is a top-layer
16
+ * `popover`, flipped above the trigger when there is more room. `input` then `change` bubble on a
17
+ * user choice; setting a property is silent. Form-associated.
12
18
  */
13
19
  export declare const defineSelect: import("./registry.js").Definer;
package/dist/smartart.js CHANGED
@@ -1,3 +1,3 @@
1
- export { defineSmartArt, renderDiagramDrawing } from './chunk-SDFQ7U2I.js';
2
- import './chunk-D6EREKYF.js';
3
- import './chunk-MDW2Y5UN.js';
1
+ export { defineSmartArt, renderDiagramDrawing } from './chunk-4WUYOQRL.js';
2
+ import './chunk-3BHP4IFP.js';
3
+ import './chunk-BDJW2KDI.js';
package/dist/theme.js CHANGED
@@ -1,2 +1,2 @@
1
- export { THEME_CSS, THEME_STYLE_ID, installOfficeUiTheme } from './chunk-LI6OV55P.js';
2
- export { COMPACT, FOCUS_RING, OFFICE_TOKENS, TOUCH, tok } from './chunk-MDW2Y5UN.js';
1
+ export { THEME_CSS, THEME_STYLE_ID, installOfficeUiTheme } from './chunk-7CCQN3VM.js';
2
+ export { COMPACT, FOCUS_RING, OFFICE_TOKENS, TOUCH, tok } from './chunk-BDJW2KDI.js';
package/dist/tokens.d.ts CHANGED
@@ -162,6 +162,15 @@ export declare const OFFICE_TOKENS: {
162
162
  readonly '--office-search-popup-min-width': "240px";
163
163
  readonly '--office-search-popup-max-height': "320px";
164
164
  readonly '--office-select-max-height': "240px";
165
+ readonly '--office-select-background': "var(--office-background, #ffffff)";
166
+ readonly '--office-select-menu-min-width': "90px";
167
+ readonly '--office-select-option-active': "color-mix(in srgb, var(--office-accent, #2563eb) 20%, var(--office-popover, #ffffff))";
168
+ readonly '--office-select-ribbon-menu-width': "192px";
169
+ readonly '--office-font-picker-max-height': "320px";
170
+ readonly '--office-font-picker-width': "256px";
171
+ readonly '--office-chevron-size': "6px";
172
+ readonly '--office-ribbon-combo-height': "28px";
173
+ readonly '--office-ribbon-icon-select-width': "36px";
165
174
  readonly '--office-search-width': "320px";
166
175
  readonly '--office-print-preview-width': "420px";
167
176
  readonly '--office-find-max-width': "640px";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ooxml-ui",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Shared, format-neutral web components and styles for the Office viewers (Word, PowerPoint, Excel)",
5
5
  "keywords": [
6
6
  "custom-elements",