ooxml-ui 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,24 +58,30 @@ 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-presence` | `participants`, `max`, `label` | `office-presence-select` `{ id }` |
77
- | `office-ui-smartart` | `drawing` (core `DiagramDrawing`), `schemeColors`, `label` | `office-smartart-render` (render report) |
78
- | `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` | `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-presence` | `participants`, `max`, `label` | `office-presence-select` `{ id }` |
79
+ | `office-ui-smartart` | `drawing` (core `DiagramDrawing`), `schemeColors`, `label` | `office-smartart-render` (render report) |
80
+ | `office-ui-icon` | `name`, `label` | none |
81
+
82
+ The local profile behind `office-ui-account` is stored on the device only, under one suite-wide
83
+ key (`readOfficeProfile`, `writeOfficeProfile`, `clearOfficeProfile`), so every Office viewer on
84
+ an origin shows the same name and avatar colour.
79
85
 
80
86
  `attachKeyTips(scope)` (from `ooxml-ui/controls`) adds Office KeyTips to any element or shadow
81
87
  root: press and release Alt to show badges on controls marked `data-keytip="H"`, type a badge to
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Office File > Account: the local display profile (name and avatar colour) shared by the suite.
3
+ * Moved from pptx-viewer `render/account.ts`. The profile is cosmetic and local: it is stored on
4
+ * this device only and never sent anywhere, except as the collaborator identity a product passes
5
+ * to a live session the user starts.
6
+ */
7
+ export interface OfficeProfile {
8
+ displayName: string;
9
+ /** CSS colour of the avatar bubble, e.g. `'#c2431f'`. */
10
+ avatarColor: string;
11
+ /** Character(s) in the avatar bubble; derived from `displayName` when omitted. */
12
+ initial?: string;
13
+ }
14
+ export declare const DEFAULT_OFFICE_PROFILE: OfficeProfile;
15
+ /** Suggested avatar colours (indigo, vermilion, cyan, green, amber, pink, slate). */
16
+ export declare const OFFICE_AVATAR_SWATCHES: readonly string[];
17
+ /** One key for the whole suite, so Word, PowerPoint, Excel and Visio on one origin share it. */
18
+ export declare const OFFICE_PROFILE_STORAGE_KEY = "ooxml-office-profile";
19
+ export type OfficeProfileChangeEvent = CustomEvent<{
20
+ profile: OfficeProfile;
21
+ }>;
22
+ /** Validate untrusted stored data into a profile. */
23
+ export declare function sanitizeOfficeProfile(raw: unknown): OfficeProfile;
24
+ /** The avatar bubble text: explicit initial, else initials of the name, else `?`. */
25
+ export declare function profileInitials(profile: OfficeProfile): string;
26
+ type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
27
+ /** Read the stored profile; the default when storage is unavailable, empty or corrupt. */
28
+ export declare function readOfficeProfile(storage?: StorageLike | undefined, key?: string): OfficeProfile;
29
+ /** Persist the profile on this device. Silently does nothing when storage is unavailable. */
30
+ export declare function writeOfficeProfile(profile: OfficeProfile, storage?: StorageLike | undefined, key?: string): void;
31
+ /** Forget the stored profile. */
32
+ export declare function clearOfficeProfile(storage?: StorageLike | undefined, key?: string): void;
33
+ /**
34
+ * `<office-ui-account>`: User Information with avatar, display name and avatar colour. Set or read
35
+ * `profile`; edits emit `office-profile-change`. `slot="sign-in"` hosts a product sign-in flow;
36
+ * the default slot holds product sections (theme, product information).
37
+ */
38
+ export declare const defineAccount: import("./registry.js").Definer;
39
+ export {};
@@ -1,8 +1,189 @@
1
1
  import { createIconSvg, paintIcon, defineIcon } from './chunk-PCSUQPMC.js';
2
+ import { initialsOf } from './chunk-RLPL7Q6J.js';
2
3
  import { definer, attachStyles, controlCss, emit } from './chunk-EY266IK3.js';
3
4
 
4
- // src/button.ts
5
+ // src/account.ts
6
+ var DEFAULT_OFFICE_PROFILE = { displayName: "", avatarColor: "#6366f1" };
7
+ var OFFICE_AVATAR_SWATCHES = [
8
+ "#6366f1",
9
+ "#c2431f",
10
+ "#0891b2",
11
+ "#16a34a",
12
+ "#ca8a04",
13
+ "#db2777",
14
+ "#64748b"
15
+ ];
16
+ var OFFICE_PROFILE_STORAGE_KEY = "ooxml-office-profile";
17
+ var COLOR = /^#[0-9a-f]{6}$/i;
18
+ function sanitizeOfficeProfile(raw) {
19
+ const value = raw && typeof raw === "object" ? raw : {};
20
+ const displayName = typeof value.displayName === "string" ? value.displayName.trim().slice(0, 64) : "";
21
+ const avatarColor = typeof value.avatarColor === "string" && COLOR.test(value.avatarColor) ? value.avatarColor : DEFAULT_OFFICE_PROFILE.avatarColor;
22
+ const initial = typeof value.initial === "string" && value.initial.trim() ? value.initial.trim().slice(0, 2) : void 0;
23
+ return { displayName, avatarColor, ...initial ? { initial } : {} };
24
+ }
25
+ function profileInitials(profile) {
26
+ return (profile.initial ?? initialsOf(profile.displayName)).toUpperCase();
27
+ }
28
+ var defaultStorage = () => {
29
+ try {
30
+ return typeof localStorage === "undefined" ? void 0 : localStorage;
31
+ } catch {
32
+ return void 0;
33
+ }
34
+ };
35
+ function readOfficeProfile(storage = defaultStorage(), key = OFFICE_PROFILE_STORAGE_KEY) {
36
+ try {
37
+ const raw = storage?.getItem(key);
38
+ return raw ? sanitizeOfficeProfile(JSON.parse(raw)) : { ...DEFAULT_OFFICE_PROFILE };
39
+ } catch {
40
+ return { ...DEFAULT_OFFICE_PROFILE };
41
+ }
42
+ }
43
+ function writeOfficeProfile(profile, storage = defaultStorage(), key = OFFICE_PROFILE_STORAGE_KEY) {
44
+ try {
45
+ storage?.setItem(key, JSON.stringify(sanitizeOfficeProfile(profile)));
46
+ } catch {
47
+ }
48
+ }
49
+ function clearOfficeProfile(storage = defaultStorage(), key = OFFICE_PROFILE_STORAGE_KEY) {
50
+ try {
51
+ storage?.removeItem(key);
52
+ } catch {
53
+ }
54
+ }
5
55
  var CSS = `
56
+ :host { display: block; font-family: var(--office-font, system-ui, sans-serif); font-size: 13px; }
57
+ h2 { margin: 0 0 12px; font-size: 15px; font-weight: 600; }
58
+ .who { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
59
+ .avatar { display: grid; place-items: center; width: 48px; height: 48px; border-radius: 50%;
60
+ color: #fff; font-weight: 600; font-size: 18px; flex: none; }
61
+ .name { font-size: 14px; font-weight: 600; }
62
+ .note { margin: 2px 0 0; color: var(--office-muted, #6b7280); font-size: 12px; }
63
+ label { display: grid; gap: 4px; max-width: 320px; margin-bottom: 12px; }
64
+ input { min-height: 28px; box-sizing: border-box; padding: 2px 6px; font: inherit; color: inherit;
65
+ border: 1px solid var(--office-border, #d1d5db); border-radius: 3px; background: var(--office-background, #fff); }
66
+ .swatches { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 12px; }
67
+ .swatches button { width: var(--office-target-size, 28px); height: var(--office-target-size, 28px); padding: 0;
68
+ border: 2px solid transparent; border-radius: 50%; cursor: pointer; }
69
+ .swatches button[aria-checked="true"] { border-color: var(--office-foreground, #1f2937);
70
+ box-shadow: inset 0 0 0 2px var(--office-background, #fff); }
71
+ .swatches button:focus-visible, input:focus-visible { outline: 2px solid var(--office-ring, #2563eb); outline-offset: 1px; }
72
+ @media (forced-colors: active) { .swatches button { forced-color-adjust: none; } }
73
+ `;
74
+ var defineAccount = definer("office-ui-account", () => {
75
+ class OfficeUiAccount extends HTMLElement {
76
+ #profile = { ...DEFAULT_OFFICE_PROFILE };
77
+ #avatar;
78
+ #name;
79
+ #input;
80
+ #swatches;
81
+ constructor() {
82
+ super();
83
+ const doc = this.ownerDocument;
84
+ const root = this.attachShadow({ mode: "open" });
85
+ attachStyles(root, controlCss(CSS));
86
+ const heading = doc.createElement("h2");
87
+ heading.textContent = "User Information";
88
+ const who = doc.createElement("div");
89
+ who.className = "who";
90
+ this.#avatar = doc.createElement("div");
91
+ this.#avatar.className = "avatar";
92
+ this.#avatar.setAttribute("aria-hidden", "true");
93
+ const details = doc.createElement("div");
94
+ this.#name = doc.createElement("div");
95
+ this.#name.className = "name";
96
+ const note = doc.createElement("p");
97
+ note.className = "note";
98
+ note.textContent = "Stored on this device only. Never sent anywhere.";
99
+ details.append(this.#name, note);
100
+ who.append(this.#avatar, details);
101
+ const field = doc.createElement("label");
102
+ field.append("Display name");
103
+ this.#input = doc.createElement("input");
104
+ this.#input.type = "text";
105
+ this.#input.maxLength = 64;
106
+ this.#input.autocomplete = "name";
107
+ this.#input.addEventListener(
108
+ "change",
109
+ () => this.#update({ ...this.#profile, displayName: this.#input.value.trim() })
110
+ );
111
+ field.append(this.#input);
112
+ const colorLabel = doc.createElement("div");
113
+ colorLabel.id = "color-label";
114
+ colorLabel.textContent = "Avatar colour";
115
+ this.#swatches = doc.createElement("div");
116
+ this.#swatches.className = "swatches";
117
+ this.#swatches.setAttribute("role", "radiogroup");
118
+ this.#swatches.setAttribute("aria-labelledby", "color-label");
119
+ for (const color of OFFICE_AVATAR_SWATCHES) {
120
+ const swatch = doc.createElement("button");
121
+ swatch.type = "button";
122
+ swatch.setAttribute("role", "radio");
123
+ swatch.setAttribute("aria-label", color);
124
+ swatch.dataset.color = color;
125
+ swatch.style.background = color;
126
+ swatch.addEventListener(
127
+ "click",
128
+ () => this.#update({ ...this.#profile, avatarColor: color })
129
+ );
130
+ this.#swatches.append(swatch);
131
+ }
132
+ this.#swatches.addEventListener("keydown", (event) => this.#swatchKey(event));
133
+ const signIn = doc.createElement("slot");
134
+ signIn.name = "sign-in";
135
+ root.append(
136
+ heading,
137
+ who,
138
+ field,
139
+ colorLabel,
140
+ this.#swatches,
141
+ signIn,
142
+ doc.createElement("slot")
143
+ );
144
+ this.#sync();
145
+ }
146
+ get profile() {
147
+ return { ...this.#profile };
148
+ }
149
+ set profile(value) {
150
+ this.#profile = sanitizeOfficeProfile(value);
151
+ this.#sync();
152
+ }
153
+ #update(next) {
154
+ this.#profile = sanitizeOfficeProfile(next);
155
+ this.#sync();
156
+ emit(this, "office-profile-change", { profile: this.profile });
157
+ }
158
+ #swatchKey(event) {
159
+ const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[event.key];
160
+ if (!step) return;
161
+ event.preventDefault();
162
+ const colors = OFFICE_AVATAR_SWATCHES;
163
+ const at = Math.max(0, colors.indexOf(this.#profile.avatarColor));
164
+ const color = colors[(at + step + colors.length) % colors.length];
165
+ this.#update({ ...this.#profile, avatarColor: color });
166
+ this.#swatches.querySelector(`[data-color="${color}"]`)?.focus();
167
+ }
168
+ #sync() {
169
+ const { displayName, avatarColor } = this.#profile;
170
+ this.#avatar.textContent = profileInitials(this.#profile);
171
+ this.#avatar.style.background = avatarColor;
172
+ this.#name.textContent = displayName || "No display name";
173
+ if (this.#input.value.trim() !== displayName) this.#input.value = displayName;
174
+ const known = OFFICE_AVATAR_SWATCHES.includes(avatarColor);
175
+ this.#swatches.querySelectorAll('[role="radio"]').forEach((swatch, index) => {
176
+ const checked = swatch.dataset.color === avatarColor;
177
+ swatch.setAttribute("aria-checked", String(checked));
178
+ swatch.tabIndex = checked || !known && index === 0 ? 0 : -1;
179
+ });
180
+ }
181
+ }
182
+ return OfficeUiAccount;
183
+ });
184
+
185
+ // src/button.ts
186
+ var CSS2 = `
6
187
  :host { display: inline-flex; vertical-align: middle; }
7
188
  button { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
8
189
  gap: 6px; min-width: var(--office-target-size, 28px); min-height: var(--office-target-size, 28px);
@@ -43,7 +224,7 @@ var defineButton = definer("office-ui-button", () => {
43
224
  constructor() {
44
225
  super();
45
226
  const root = this.attachShadow({ mode: "open", delegatesFocus: true });
46
- attachStyles(root, controlCss(CSS));
227
+ attachStyles(root, controlCss(CSS2));
47
228
  this.button = this.ownerDocument.createElement("button");
48
229
  this.button.type = "button";
49
230
  this.button.setAttribute("part", "button");
@@ -218,12 +399,12 @@ var defineCheckbox = definer(
218
399
  var defineSwitch = definer("office-ui-switch", makeCheckable("switch", SWITCH_CSS));
219
400
 
220
401
  // src/dialog.ts
221
- var CSS2 = `
402
+ var CSS3 = `
222
403
  :host { display: none; position: fixed; inset: 0; z-index: 1000; align-items: center; justify-content: center; }
223
404
  :host([open]) { display: flex; }
224
405
  .backdrop { position: absolute; inset: 0; background: rgb(0 0 0 / .4); }
225
406
  .box { position: relative; box-sizing: border-box; display: flex; flex-direction: column;
226
- min-width: min(320px, 100vw); max-width: min(640px, 100vw); max-height: 90vh;
407
+ width: var(--office-dialog-width, auto); min-width: min(320px, 100vw); max-width: var(--office-dialog-max-width, min(640px, 100vw)); max-height: 90vh;
227
408
  background: var(--office-background, #fff); color: var(--office-foreground, #1f2937);
228
409
  border: 1px solid var(--office-border, #d1d5db); border-radius: 8px; font-family: var(--office-font, system-ui, sans-serif); }
229
410
  header { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 16px;
@@ -251,7 +432,7 @@ var defineDialog = definer("office-ui-dialog", () => {
251
432
  super();
252
433
  const doc = this.ownerDocument;
253
434
  const root = this.attachShadow({ mode: "open" });
254
- attachStyles(root, controlCss(CSS2));
435
+ attachStyles(root, controlCss(CSS3));
255
436
  const backdrop = doc.createElement("div");
256
437
  backdrop.className = "backdrop";
257
438
  this.box = doc.createElement("div");
@@ -354,7 +535,7 @@ var defineDialog = definer("office-ui-dialog", () => {
354
535
  });
355
536
 
356
537
  // src/command-search.ts
357
- var CSS3 = `
538
+ var CSS4 = `
358
539
  :host { display: inline-flex; align-items: center; min-width: 0; font-family: var(--office-font, system-ui, sans-serif); }
359
540
  .field { display: flex; align-items: center; gap: 6px; width: 100%; min-width: 0; height: var(--office-target-size, 28px);
360
541
  padding: 0 8px; box-sizing: border-box; border: 1px solid transparent; border-radius: var(--office-radius, 4px);
@@ -394,7 +575,7 @@ var defineCommandSearch = definer("office-ui-command-search", () => {
394
575
  super();
395
576
  const doc = this.ownerDocument;
396
577
  const root = this.attachShadow({ mode: "open", delegatesFocus: true });
397
- attachStyles(root, controlCss(CSS3));
578
+ attachStyles(root, controlCss(CSS4));
398
579
  const field = doc.createElement("div");
399
580
  field.className = "field";
400
581
  const glass = createIconSvg(doc);
@@ -551,7 +732,7 @@ var defineCommandSearch = definer("office-ui-command-search", () => {
551
732
  });
552
733
 
553
734
  // src/context-menu.ts
554
- var CSS4 = `
735
+ var CSS5 = `
555
736
  :host { display: contents; }
556
737
  .panel { position: fixed; inset: auto; margin: 0; min-width: 200px; padding: 4px; box-sizing: border-box;
557
738
  background: var(--office-background, #fff); color: var(--office-foreground, #1f2937);
@@ -587,7 +768,7 @@ var defineContextMenu = definer("office-ui-context-menu", () => {
587
768
  super();
588
769
  const doc = this.ownerDocument;
589
770
  const root = this.attachShadow({ mode: "open" });
590
- attachStyles(root, controlCss(CSS4));
771
+ attachStyles(root, controlCss(CSS5));
591
772
  this.panel = doc.createElement("div");
592
773
  this.panel.className = "panel";
593
774
  this.panel.setAttribute("role", "menu");
@@ -992,6 +1173,271 @@ var defineMenuButton = definer("office-ui-menu-button", () => {
992
1173
  return OfficeUiMenuButton;
993
1174
  });
994
1175
 
1176
+ // src/options.ts
1177
+ function clampOptionNumber(raw, min, max) {
1178
+ const parsed = Number(raw);
1179
+ return Number.isFinite(parsed) && raw.trim() !== "" ? Math.min(max, Math.max(min, parsed)) : void 0;
1180
+ }
1181
+ var CSS6 = `
1182
+ :host { display: contents; }
1183
+ office-ui-dialog { --office-dialog-width: min(820px, 100vw); --office-dialog-max-width: min(820px, 100vw); }
1184
+ .layout { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 0; min-height: min(440px, 70vh); margin: -16px; }
1185
+ nav { display: flex; flex-direction: column; padding: 8px 0; border-inline-end: 1px solid var(--office-border, #d1d5db);
1186
+ background: var(--office-surface, #f3f4f6); overflow: auto; }
1187
+ nav button { min-height: 30px; padding: 4px 14px; border: 0; border-radius: 0; background: transparent; color: inherit;
1188
+ font: inherit; text-align: start; cursor: pointer; }
1189
+ nav button[aria-selected="true"] { background: var(--office-selected, #e5e7eb); font-weight: 600;
1190
+ box-shadow: inset 3px 0 0 var(--office-accent, #2563eb); }
1191
+ nav button:focus-visible { outline: 2px solid var(--office-ring, #2563eb); outline-offset: -2px; }
1192
+ [role="tabpanel"] { padding: 16px 20px; overflow: auto; }
1193
+ .lead { margin: 0 0 12px; font-size: 14px; }
1194
+ .unavailable { margin: 0 0 12px; color: var(--office-muted, #6b7280); }
1195
+ section + section { margin-top: 16px; }
1196
+ h3 { margin: 0 0 8px; padding-bottom: 4px; border-bottom: 1px solid var(--office-border, #d1d5db); font-size: 13px; font-weight: 600; }
1197
+ .desc { margin: 0 0 8px; color: var(--office-muted, #6b7280); }
1198
+ .row { display: flex; align-items: center; gap: 8px; min-height: 30px; }
1199
+ .row.indent { padding-inline-start: 24px; }
1200
+ .row.field > span:first-child { min-width: 180px; }
1201
+ .row[data-disabled] { color: var(--office-muted, #6b7280); }
1202
+ input[type="text"], input[type="number"], select { min-height: 26px; box-sizing: border-box; font: inherit;
1203
+ border: 1px solid var(--office-border, #d1d5db); border-radius: 3px; background: var(--office-background, #fff); color: inherit; }
1204
+ input[type="number"] { width: 80px; }
1205
+ .info { display: inline-grid; place-items: center; width: 15px; height: 15px; border: 1px solid currentColor;
1206
+ border-radius: 50%; font-size: 10px; color: var(--office-muted, #6b7280); cursor: help; }
1207
+ .actions { display: flex; gap: 8px; }
1208
+ .actions button { min-width: 76px; min-height: 28px; font: inherit; cursor: pointer; }
1209
+ @media (max-width: 600px) {
1210
+ .layout { grid-template-columns: 1fr; }
1211
+ nav { flex-direction: row; border-inline-end: 0; border-bottom: 1px solid var(--office-border, #d1d5db); }
1212
+ nav button { white-space: nowrap; }
1213
+ .row.field { flex-wrap: wrap; }
1214
+ }
1215
+ `;
1216
+ var defineOptionsDialog = definer("office-ui-options-dialog", () => {
1217
+ class OfficeUiOptionsDialog extends HTMLElement {
1218
+ static observedAttributes = ["open", "heading"];
1219
+ #categories = [];
1220
+ #values = {};
1221
+ #draft = {};
1222
+ #active = "";
1223
+ #dialog;
1224
+ #nav;
1225
+ #pane;
1226
+ constructor() {
1227
+ super();
1228
+ const doc = this.ownerDocument;
1229
+ const root = this.attachShadow({ mode: "open" });
1230
+ attachStyles(root, controlCss(CSS6));
1231
+ this.#dialog = doc.createElement("office-ui-dialog");
1232
+ const layout = doc.createElement("div");
1233
+ layout.className = "layout";
1234
+ this.#nav = doc.createElement("nav");
1235
+ this.#nav.setAttribute("role", "tablist");
1236
+ this.#nav.setAttribute("aria-orientation", "vertical");
1237
+ this.#nav.setAttribute("aria-label", "Categories");
1238
+ this.#pane = doc.createElement("div");
1239
+ this.#pane.setAttribute("role", "tabpanel");
1240
+ this.#pane.id = "pane";
1241
+ layout.append(this.#nav, this.#pane);
1242
+ const actions = doc.createElement("div");
1243
+ actions.slot = "footer";
1244
+ actions.className = "actions";
1245
+ const ok = this.#button("OK", () => this.#commit());
1246
+ ok.dataset.action = "ok";
1247
+ const cancel = this.#button("Cancel", () => this.#closeDialog());
1248
+ cancel.dataset.action = "cancel";
1249
+ actions.append(ok, cancel);
1250
+ this.#dialog.append(layout, actions);
1251
+ root.append(this.#dialog);
1252
+ this.#dialog.addEventListener("office-dialog-close", (event) => {
1253
+ if (!event.defaultPrevented) this.removeAttribute("open");
1254
+ });
1255
+ this.#nav.addEventListener("keydown", (event) => this.#navKey(event));
1256
+ }
1257
+ get categories() {
1258
+ return this.#categories;
1259
+ }
1260
+ set categories(value) {
1261
+ this.#categories = value;
1262
+ if (!value.some((category) => category.id === this.#active))
1263
+ this.#active = value[0]?.id ?? "";
1264
+ this.#render();
1265
+ }
1266
+ get values() {
1267
+ return { ...this.#values };
1268
+ }
1269
+ set values(value) {
1270
+ this.#values = { ...value };
1271
+ if (!this.open) this.#draft = { ...value };
1272
+ this.#render();
1273
+ }
1274
+ /** The selected category id. */
1275
+ get category() {
1276
+ return this.#active;
1277
+ }
1278
+ set category(id) {
1279
+ if (this.#categories.some((category) => category.id === id)) {
1280
+ this.#active = id;
1281
+ this.#render();
1282
+ }
1283
+ }
1284
+ get open() {
1285
+ return this.hasAttribute("open");
1286
+ }
1287
+ set open(value) {
1288
+ this.toggleAttribute("open", Boolean(value));
1289
+ }
1290
+ show() {
1291
+ this.open = true;
1292
+ }
1293
+ close() {
1294
+ this.#closeDialog();
1295
+ }
1296
+ attributeChangedCallback(name, old, value) {
1297
+ if (name === "heading") this.#dialog.setAttribute("heading", value ?? "Options");
1298
+ if (name === "open" && old === null !== (value === null)) {
1299
+ if (value !== null) {
1300
+ this.#draft = { ...this.#values };
1301
+ this.#render();
1302
+ }
1303
+ this.#dialog.open = value !== null;
1304
+ }
1305
+ }
1306
+ connectedCallback() {
1307
+ if (!this.hasAttribute("heading")) this.#dialog.setAttribute("heading", "Options");
1308
+ }
1309
+ #button(label, run) {
1310
+ const button = this.ownerDocument.createElement("button");
1311
+ button.type = "button";
1312
+ button.textContent = label;
1313
+ button.addEventListener("click", run);
1314
+ return button;
1315
+ }
1316
+ #closeDialog() {
1317
+ this.removeAttribute("open");
1318
+ }
1319
+ #commit() {
1320
+ const changed = Object.keys(this.#draft).filter(
1321
+ (key) => this.#draft[key] !== this.#values[key]
1322
+ );
1323
+ this.#values = { ...this.#draft };
1324
+ emit(this, "office-options-change", { values: { ...this.#values }, changed });
1325
+ this.#closeDialog();
1326
+ }
1327
+ #navKey(event) {
1328
+ const tabs = [...this.#nav.querySelectorAll('[role="tab"]')];
1329
+ const at = tabs.findIndex((tab) => tab.getAttribute("aria-selected") === "true");
1330
+ const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
1331
+ const target = event.key === "Home" ? tabs[0] : event.key === "End" ? tabs.at(-1) : step ? tabs[(at + step + tabs.length) % tabs.length] : void 0;
1332
+ if (!target) return;
1333
+ event.preventDefault();
1334
+ this.category = target.dataset.category;
1335
+ this.#nav.querySelector('[aria-selected="true"]')?.focus();
1336
+ }
1337
+ #render() {
1338
+ const doc = this.ownerDocument;
1339
+ this.#nav.replaceChildren(
1340
+ ...this.#categories.map((category2) => {
1341
+ const tab = this.#button(category2.label, () => this.category = category2.id);
1342
+ tab.setAttribute("role", "tab");
1343
+ tab.dataset.category = category2.id;
1344
+ tab.id = `tab-${category2.id}`;
1345
+ tab.setAttribute("aria-controls", "pane");
1346
+ const selected = category2.id === this.#active;
1347
+ tab.setAttribute("aria-selected", String(selected));
1348
+ tab.tabIndex = selected ? 0 : -1;
1349
+ return tab;
1350
+ })
1351
+ );
1352
+ const category = this.#categories.find((entry) => entry.id === this.#active);
1353
+ this.#pane.replaceChildren();
1354
+ if (!category) return;
1355
+ this.#pane.setAttribute("aria-labelledby", `tab-${category.id}`);
1356
+ if (category.description) this.#pane.append(text(doc, "p", category.description, "lead"));
1357
+ if (category.disabled) this.#pane.append(text(doc, "p", category.disabled, "unavailable"));
1358
+ for (const section of category.sections) {
1359
+ const block = doc.createElement("section");
1360
+ block.append(text(doc, "h3", section.title));
1361
+ if (section.description) block.append(text(doc, "p", section.description, "desc"));
1362
+ for (const control of section.controls) block.append(this.#control(control));
1363
+ this.#pane.append(block);
1364
+ }
1365
+ }
1366
+ #control(control) {
1367
+ const doc = this.ownerDocument;
1368
+ const row = doc.createElement("label");
1369
+ row.className = `row${control.indent ? " indent" : ""}${control.kind === "toggle" ? "" : " field"}`;
1370
+ row.dataset.key = control.key;
1371
+ const value = this.#draft[control.key];
1372
+ const set = (next) => this.#draft[control.key] = next;
1373
+ let input;
1374
+ if (control.kind === "toggle") {
1375
+ const box = doc.createElement("input");
1376
+ box.type = "checkbox";
1377
+ box.checked = value === true;
1378
+ box.addEventListener("change", () => set(box.checked));
1379
+ input = box;
1380
+ row.append(box, text(doc, "span", control.label));
1381
+ } else if (control.kind === "select") {
1382
+ const select = doc.createElement("select");
1383
+ for (const choice of control.choices) {
1384
+ const option = doc.createElement("option");
1385
+ option.value = choice.value;
1386
+ option.textContent = choice.label;
1387
+ select.append(option);
1388
+ }
1389
+ select.value = String(value ?? control.choices[0]?.value ?? "");
1390
+ select.addEventListener("change", () => set(select.value));
1391
+ input = select;
1392
+ row.append(text(doc, "span", control.label), select);
1393
+ } else {
1394
+ const field = doc.createElement("input");
1395
+ field.type = control.kind === "number" ? "number" : "text";
1396
+ field.value = value === void 0 ? "" : String(value);
1397
+ if (control.kind === "number") {
1398
+ field.min = String(control.min);
1399
+ field.max = String(control.max);
1400
+ if (control.step) field.step = String(control.step);
1401
+ field.addEventListener("change", () => {
1402
+ const next = clampOptionNumber(field.value, control.min, control.max);
1403
+ if (next === void 0) field.value = String(this.#draft[control.key] ?? "");
1404
+ else {
1405
+ field.value = String(next);
1406
+ set(next);
1407
+ }
1408
+ });
1409
+ } else {
1410
+ if (control.maxLength) field.maxLength = control.maxLength;
1411
+ field.addEventListener("input", () => set(field.value));
1412
+ }
1413
+ input = field;
1414
+ row.append(text(doc, "span", control.label), field);
1415
+ if (control.kind === "number" && control.unit) row.append(text(doc, "span", control.unit));
1416
+ }
1417
+ if (control.disabled) {
1418
+ input.disabled = true;
1419
+ row.dataset.disabled = "";
1420
+ row.title = control.disabled;
1421
+ }
1422
+ if (control.info) {
1423
+ const info = text(doc, "span", "i", "info");
1424
+ info.title = control.info;
1425
+ info.setAttribute("role", "img");
1426
+ info.setAttribute("aria-label", control.info);
1427
+ row.append(info);
1428
+ }
1429
+ return row;
1430
+ }
1431
+ }
1432
+ return OfficeUiOptionsDialog;
1433
+ });
1434
+ function text(doc, tag, content, className) {
1435
+ const el = doc.createElement(tag);
1436
+ el.textContent = content;
1437
+ if (className) el.className = className;
1438
+ return el;
1439
+ }
1440
+
995
1441
  // src/ribbon.ts
996
1442
  var GROUP_CSS = `
997
1443
  :host { display: inline-flex; flex: none; align-self: stretch; }
@@ -1126,7 +1572,7 @@ var defineToolbar = definer("office-ui-toolbar", () => {
1126
1572
  });
1127
1573
 
1128
1574
  // src/select.ts
1129
- var CSS5 = `
1575
+ var CSS7 = `
1130
1576
  :host { display: inline-block; position: relative; vertical-align: middle; }
1131
1577
  button { box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: 8px;
1132
1578
  width: 100%; min-height: var(--office-target-size, 28px); padding: 0 8px; font: inherit; font-size: 12px;
@@ -1165,7 +1611,7 @@ var defineSelect = definer("office-ui-select", () => {
1165
1611
  super();
1166
1612
  const doc = this.ownerDocument;
1167
1613
  const root = this.attachShadow({ mode: "open", delegatesFocus: true });
1168
- attachStyles(root, controlCss(CSS5));
1614
+ attachStyles(root, controlCss(CSS7));
1169
1615
  this.trigger = doc.createElement("button");
1170
1616
  this.trigger.type = "button";
1171
1617
  this.trigger.setAttribute("role", "combobox");
@@ -1416,7 +1862,7 @@ var defineStatusItem = definer("office-ui-status-item", () => {
1416
1862
  });
1417
1863
 
1418
1864
  // src/tab-strip.ts
1419
- var CSS6 = `
1865
+ var CSS8 = `
1420
1866
  :host { display: flex; align-items: stretch; min-width: 0; min-height: 28px; gap: 2px; padding: 0 6px;
1421
1867
  background: var(--office-surface, #f3f4f6); color: var(--office-muted-foreground, #6b7280);
1422
1868
  border-top: 1px solid var(--office-border, #d1d5db); font-size: 12px;
@@ -1463,7 +1909,7 @@ var defineTabStrip = definer("office-ui-tab-strip", () => {
1463
1909
  super();
1464
1910
  const doc = this.ownerDocument;
1465
1911
  const root = this.attachShadow({ mode: "open" });
1466
- attachStyles(root, controlCss(CSS6));
1912
+ attachStyles(root, controlCss(CSS8));
1467
1913
  const step = (name, icon) => {
1468
1914
  const button = doc.createElement("button");
1469
1915
  button.type = "button";
@@ -1602,7 +2048,7 @@ var defineTabStrip = definer("office-ui-tab-strip", () => {
1602
2048
  });
1603
2049
 
1604
2050
  // src/zoom-slider.ts
1605
- var CSS7 = `
2051
+ var CSS9 = `
1606
2052
  :host { display: inline-flex; align-items: center; gap: 2px; font-size: 11px;
1607
2053
  color: var(--office-foreground, #1f2937); font-family: var(--office-font, system-ui, sans-serif); }
1608
2054
  button { box-sizing: border-box; display: inline-grid; place-items: center; min-width: var(--office-target-size, 24px);
@@ -1638,13 +2084,13 @@ var defineZoomSlider = definer("office-ui-zoom-slider", () => {
1638
2084
  super();
1639
2085
  const doc = this.ownerDocument;
1640
2086
  const root = this.attachShadow({ mode: "open" });
1641
- attachStyles(root, controlCss(CSS7));
1642
- const button = (name, text) => {
2087
+ attachStyles(root, controlCss(CSS9));
2088
+ const button = (name, text2) => {
1643
2089
  const element = doc.createElement("button");
1644
2090
  element.type = "button";
1645
2091
  element.setAttribute("aria-label", name);
1646
2092
  element.title = name;
1647
- element.textContent = text;
2093
+ element.textContent = text2;
1648
2094
  return element;
1649
2095
  };
1650
2096
  this.out = button("Zoom out", "\u2212");
@@ -1885,10 +2331,12 @@ var CONTROL_DEFINERS = [
1885
2331
  defineStatusBar,
1886
2332
  defineStatusItem,
1887
2333
  defineZoomSlider,
1888
- defineTabStrip
2334
+ defineTabStrip,
2335
+ defineOptionsDialog,
2336
+ defineAccount
1889
2337
  ];
1890
2338
  function registerControls(registry) {
1891
2339
  for (const define of CONTROL_DEFINERS) define(registry);
1892
2340
  }
1893
2341
 
1894
- export { CONTROL_DEFINERS, attachKeyTips, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineMenuButton, defineMenuItem, defineMenuSeparator, defineRibbonGroup, defineRibbonStack, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, registerControls };
2342
+ export { CONTROL_DEFINERS, DEFAULT_OFFICE_PROFILE, OFFICE_AVATAR_SWATCHES, OFFICE_PROFILE_STORAGE_KEY, attachKeyTips, clampOptionNumber, clearOfficeProfile, defineAccount, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineMenuButton, defineMenuItem, defineMenuSeparator, defineOptionsDialog, defineRibbonGroup, defineRibbonStack, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, profileInitials, readOfficeProfile, registerControls, sanitizeOfficeProfile, writeOfficeProfile };
@@ -1,3 +1,4 @@
1
+ import { defineAccount } from './account.js';
1
2
  import { defineButton } from './button.js';
2
3
  import { defineCheckbox, defineSwitch } from './checkable.js';
3
4
  import { defineDialog } from './dialog.js';
@@ -5,12 +6,13 @@ import { defineIcon } from './icons.js';
5
6
  import { defineCommandSearch } from './command-search.js';
6
7
  import { defineContextMenu, defineMenuSeparator } from './context-menu.js';
7
8
  import { defineMenuButton, defineMenuItem } from './menu.js';
9
+ import { defineOptionsDialog } from './options.js';
8
10
  import { defineRibbonGroup, defineRibbonStack, defineToolbar } from './ribbon.js';
9
11
  import { defineSelect } from './select.js';
10
12
  import { defineStatusBar, defineStatusItem } from './status-bar.js';
11
13
  import { defineTabStrip } from './tab-strip.js';
12
14
  import { defineZoomSlider } from './zoom-slider.js';
13
- export { defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineIcon, defineMenuButton, defineMenuItem, defineMenuSeparator, defineRibbonGroup, defineRibbonStack, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, };
15
+ export { defineAccount, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineIcon, defineMenuButton, defineMenuItem, defineMenuSeparator, defineOptionsDialog, defineRibbonGroup, defineRibbonStack, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, };
14
16
  export type { OfficeCommandEvent } from './button.js';
15
17
  export type { OfficeDialogCloseEvent, OfficeDialogCloseReason } from './dialog.js';
16
18
  export type { OfficeSelectOption } from './select.js';
@@ -18,7 +20,9 @@ export type { OfficeStatusActivateEvent } from './status-bar.js';
18
20
  export type { OfficeTab, OfficeTabSelectEvent } from './tab-strip.js';
19
21
  export type { OfficeSearchCommand } from './command-search.js';
20
22
  export { attachKeyTips, type KeyTipsHandle } from './keytips.js';
23
+ export { clearOfficeProfile, DEFAULT_OFFICE_PROFILE, OFFICE_AVATAR_SWATCHES, OFFICE_PROFILE_STORAGE_KEY, profileInitials, readOfficeProfile, sanitizeOfficeProfile, writeOfficeProfile, type OfficeProfile, type OfficeProfileChangeEvent, } from './account.js';
24
+ export { clampOptionNumber, type OfficeOptionCategory, type OfficeOptionChoice, type OfficeOptionControl, type OfficeOptionSection, type OfficeOptionsChangeEvent, type OfficeOptionValue, type OfficeOptionValues, } from './options.js';
21
25
  /** Every control of this entry. */
22
- export declare const CONTROL_DEFINERS: readonly [import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer];
26
+ export declare const CONTROL_DEFINERS: readonly [import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer, import("./registry.js").Definer];
23
27
  /** Idempotent, SSR-safe (no-op without a DOM). */
24
28
  export declare function registerControls(registry?: CustomElementRegistry): void;
package/dist/controls.js CHANGED
@@ -1,3 +1,4 @@
1
- export { CONTROL_DEFINERS, attachKeyTips, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineMenuButton, defineMenuItem, defineMenuSeparator, defineRibbonGroup, defineRibbonStack, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, registerControls } from './chunk-L5FYB6M7.js';
1
+ export { CONTROL_DEFINERS, DEFAULT_OFFICE_PROFILE, OFFICE_AVATAR_SWATCHES, OFFICE_PROFILE_STORAGE_KEY, attachKeyTips, clampOptionNumber, clearOfficeProfile, defineAccount, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineMenuButton, defineMenuItem, defineMenuSeparator, defineOptionsDialog, defineRibbonGroup, defineRibbonStack, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, profileInitials, readOfficeProfile, registerControls, sanitizeOfficeProfile, writeOfficeProfile } from './chunk-TMSFX6TC.js';
2
2
  export { defineIcon } from './chunk-PCSUQPMC.js';
3
+ import './chunk-RLPL7Q6J.js';
3
4
  import './chunk-EY266IK3.js';
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export * from './smartart.js';
5
5
  export * from './theme.js';
6
6
  export { CONTRACT_REVISION, type Definer } from './registry.js';
7
7
  /** Every ooxml-ui tag this package defines, in registration order. */
8
- export declare const OFFICE_UI_TAGS: readonly ["office-ui-icon", "office-ui-button", "office-ui-checkbox", "office-ui-switch", "office-ui-select", "office-ui-ribbon-group", "office-ui-ribbon-stack", "office-ui-menu-item", "office-ui-menu-button", "office-ui-menu-separator", "office-ui-context-menu", "office-ui-command-search", "office-ui-toolbar", "office-ui-dialog", "office-ui-status-bar", "office-ui-status-item", "office-ui-zoom-slider", "office-ui-tab-strip", "office-ui-presence", "office-ui-smartart"];
8
+ export declare const OFFICE_UI_TAGS: readonly ["office-ui-icon", "office-ui-button", "office-ui-checkbox", "office-ui-switch", "office-ui-select", "office-ui-ribbon-group", "office-ui-ribbon-stack", "office-ui-menu-item", "office-ui-menu-button", "office-ui-menu-separator", "office-ui-context-menu", "office-ui-command-search", "office-ui-toolbar", "office-ui-dialog", "office-ui-status-bar", "office-ui-status-item", "office-ui-zoom-slider", "office-ui-tab-strip", "office-ui-options-dialog", "office-ui-account", "office-ui-presence", "office-ui-smartart"];
9
9
  /**
10
10
  * Install the theme tokens and define every element. Idempotent and safe to call from every
11
11
  * viewer binding; a no-op where there is no DOM (SSR).
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { installOfficeUiTheme } from './chunk-H4KFVIGT.js';
2
2
  export { THEME_CSS, THEME_STYLE_ID, installOfficeUiTheme } from './chunk-H4KFVIGT.js';
3
- import { registerControls } from './chunk-L5FYB6M7.js';
4
- export { CONTROL_DEFINERS, attachKeyTips, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineMenuButton, defineMenuItem, defineMenuSeparator, defineRibbonGroup, defineRibbonStack, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, registerControls } from './chunk-L5FYB6M7.js';
3
+ import { registerControls } from './chunk-TMSFX6TC.js';
4
+ export { CONTROL_DEFINERS, DEFAULT_OFFICE_PROFILE, OFFICE_AVATAR_SWATCHES, OFFICE_PROFILE_STORAGE_KEY, attachKeyTips, clampOptionNumber, clearOfficeProfile, defineAccount, defineButton, defineCheckbox, defineCommandSearch, defineContextMenu, defineDialog, defineMenuButton, defineMenuItem, defineMenuSeparator, defineOptionsDialog, defineRibbonGroup, defineRibbonStack, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, profileInitials, readOfficeProfile, registerControls, sanitizeOfficeProfile, writeOfficeProfile } from './chunk-TMSFX6TC.js';
5
5
  export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-PCSUQPMC.js';
6
6
  import { definePresence } from './chunk-RLPL7Q6J.js';
7
7
  export { definePresence, initialsOf, participantsFromAwareness } from './chunk-RLPL7Q6J.js';
@@ -29,6 +29,8 @@ var OFFICE_UI_TAGS = [
29
29
  "office-ui-status-item",
30
30
  "office-ui-zoom-slider",
31
31
  "office-ui-tab-strip",
32
+ "office-ui-options-dialog",
33
+ "office-ui-account",
32
34
  "office-ui-presence",
33
35
  "office-ui-smartart"
34
36
  ];
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Office File > Options dialog. Products describe their categories as data; the element renders
3
+ * the category list, the pane and OK/Cancel, and reports the edited values. Generalised from
4
+ * pptx-viewer's `VIEWER_OPTIONS_SCHEMA` dialog (render/options) so every viewer shares one.
5
+ */
6
+ export type OfficeOptionValue = boolean | number | string;
7
+ export type OfficeOptionValues = Record<string, OfficeOptionValue>;
8
+ export interface OfficeOptionChoice {
9
+ value: string;
10
+ label: string;
11
+ }
12
+ interface OptionBase {
13
+ /** Key into the values record. */
14
+ key: string;
15
+ label: string;
16
+ /** Optional "(i)" tooltip, as Office's info bubbles. */
17
+ info?: string;
18
+ /** Renders indented under the preceding control. */
19
+ indent?: boolean;
20
+ /** Shown disabled with this reason, for settings the product cannot honour yet. */
21
+ disabled?: string;
22
+ }
23
+ export type OfficeOptionControl = (OptionBase & {
24
+ kind: 'toggle';
25
+ }) | (OptionBase & {
26
+ kind: 'select';
27
+ choices: readonly OfficeOptionChoice[];
28
+ }) | (OptionBase & {
29
+ kind: 'number';
30
+ min: number;
31
+ max: number;
32
+ step?: number;
33
+ unit?: string;
34
+ }) | (OptionBase & {
35
+ kind: 'text';
36
+ maxLength?: number;
37
+ });
38
+ export interface OfficeOptionSection {
39
+ id: string;
40
+ title: string;
41
+ description?: string;
42
+ controls: readonly OfficeOptionControl[];
43
+ }
44
+ export interface OfficeOptionCategory {
45
+ id: string;
46
+ label: string;
47
+ /** Headline at the top of the pane. */
48
+ description?: string;
49
+ sections: readonly OfficeOptionSection[];
50
+ /** The whole category is not available yet; the pane explains why. */
51
+ disabled?: string;
52
+ }
53
+ /** Emitted on OK with every value and the keys that changed. */
54
+ export type OfficeOptionsChangeEvent = CustomEvent<{
55
+ values: OfficeOptionValues;
56
+ changed: string[];
57
+ }>;
58
+ /** Clamp a number edit into range; `undefined` when it is not a finite number. */
59
+ export declare function clampOptionNumber(raw: string, min: number, max: number): number | undefined;
60
+ /**
61
+ * `<office-ui-options-dialog>`: set `categories` and `values`, then `show()` (or the `open`
62
+ * attribute). Edits stay in a draft until OK, which emits `office-options-change` and closes;
63
+ * Cancel, Escape and the close button discard them. `heading` defaults to "Options".
64
+ */
65
+ export declare const defineOptionsDialog: import("./registry.js").Definer;
66
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ooxml-ui",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Shared, format-neutral web components and styles for the Office viewers (Word, PowerPoint, Excel)",
5
5
  "keywords": [
6
6
  "custom-elements",
@@ -70,7 +70,7 @@
70
70
  "test:package": "node scripts/package-smoke.mjs"
71
71
  },
72
72
  "dependencies": {
73
- "ooxml-core": "^0.13.0"
73
+ "ooxml-core": "^0.14.0"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/jsdom": "^21.1.7",