ooxml-ui 0.1.11 → 0.2.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
@@ -43,14 +43,14 @@ document.body.append(button);
43
43
 
44
44
  ## API: entries (ESM only)
45
45
 
46
- | Entry | Contents |
47
- | ------------------- | --------------------------------------------------------------------------------------- |
48
- | `ooxml-ui` | everything below plus `registerOfficeUi()` and `OFFICE_UI_TAGS` |
49
- | `ooxml-ui/theme` | `THEME_CSS`, `installOfficeUiTheme()` (tokens, dark mode, forced-colors, touch targets) |
50
- | `ooxml-ui/icons` | icon registry (`registerIcon`, `getIcon`, `listIcons`) and `<office-ui-icon>` |
51
- | `ooxml-ui/controls` | button, checkbox, switch, select, ribbon group, toolbar, dialog, status bar and item |
52
- | `ooxml-ui/presence` | `<office-ui-presence>` avatar stack for collaboration awareness |
53
- | `ooxml-ui/smartart` | `<office-ui-smartart>`: draws a core `DiagramDrawing` as SVG |
46
+ | Entry | Contents |
47
+ | ------------------- | ------------------------------------------------------------------------------------------------------------ |
48
+ | `ooxml-ui` | everything below plus `registerOfficeUi()` and `OFFICE_UI_TAGS` |
49
+ | `ooxml-ui/theme` | `THEME_CSS`, `installOfficeUiTheme()` (tokens, dark mode, forced-colors, touch targets) |
50
+ | `ooxml-ui/icons` | icon registry (`registerIcon`, `getIcon`, `listIcons`) and `<office-ui-icon>` |
51
+ | `ooxml-ui/controls` | button, checkbox, switch, select, ribbon group, toolbar, dialog, status bar and item, zoom slider, tab strip |
52
+ | `ooxml-ui/presence` | `<office-ui-presence>` avatar stack for collaboration awareness |
53
+ | `ooxml-ui/smartart` | `<office-ui-smartart>`: draws a core `DiagramDrawing` as SVG |
54
54
 
55
55
  Importing an entry never touches the DOM (SSR-safe). Elements are defined by the `define*` /
56
56
  `register*` functions, which are idempotent, no-ops without a `window`, and refuse to coexist
@@ -58,18 +58,20 @@ 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"` | `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`; default slot | none |
67
- | `office-ui-toolbar` | `aria-label`, `aria-orientation`; arrow-key focus movement | none |
68
- | `office-ui-dialog` | `open`, `heading`, `dismissible="false"`, `show()`, `close()`; slots default, `footer` | `office-dialog-close` `{ reason }` (cancelable) |
69
- | `office-ui-status-bar` / `-status-item` | `label`, `value`, `interactive`, `id` | `office-status-activate` `{ id }` |
70
- | `office-ui-presence` | `participants`, `max`, `label` | `office-presence-select` `{ id }` |
71
- | `office-ui-smartart` | `drawing` (core `DiagramDrawing`), `schemeColors`, `label` | `office-smartart-render` (render report) |
72
- | `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`; default slot | none |
67
+ | `office-ui-toolbar` | `aria-label`, `aria-orientation`; arrow-key focus movement | none |
68
+ | `office-ui-dialog` | `open`, `heading`, `dismissible="false"`, `show()`, `close()`; slots default, `footer` | `office-dialog-close` `{ reason }` (cancelable) |
69
+ | `office-ui-status-bar` / `-status-item` | `label`, `value`, `interactive`, `id` | `office-status-activate` `{ id }` |
70
+ | `office-ui-zoom-slider` | `value` (percent), `min`, `max`, `step`, `disabled`, `fit` (fit button name), `label` | `input`, `change`; fit: `office-command` `{ command: 'zoom-fit' }` |
71
+ | `office-ui-tab-strip` | `tabs` `[{ id, label, title? }]`, `selected`, `label`, `disabled` | `office-tab-select` `{ id }` (cancelable) |
72
+ | `office-ui-presence` | `participants`, `max`, `label` | `office-presence-select` `{ id }` |
73
+ | `office-ui-smartart` | `drawing` (core `DiagramDrawing`), `schemeColors`, `label` | `office-smartart-render` (render report) |
74
+ | `office-ui-icon` | `name`, `label` | none |
73
75
 
74
76
  Setting a property never emits an event; only user activation does. Controls honour
75
77
  `prefers-color-scheme`, `forced-colors: active` (system colours and a visible focus ring) and
package/dist/button.d.ts CHANGED
@@ -3,7 +3,8 @@ export type OfficeCommandEvent = CustomEvent<{
3
3
  }>;
4
4
  /**
5
5
  * Command button. Attributes: `label`, `icon`, `command`, `disabled`, `pressed` (aria-pressed),
6
- * `expanded` (aria-expanded), `icon-only`, `variant="stacked"`, `title`.
6
+ * `expanded` (aria-expanded), `icon-only`, `variant="stacked"`, `title`, `keyshortcuts`
7
+ * (forwarded as aria-keyshortcuts, e.g. `Control+Z`).
7
8
  * Activation (pointer, Enter, Space) emits one bubbling, composed `office-command`
8
9
  * event with `{ command }`; the host decides what it does. Nothing happens when disabled.
9
10
  */
@@ -1,4 +1,4 @@
1
- import { createIconSvg, paintIcon, defineIcon } from './chunk-7MCSBDSU.js';
1
+ import { createIconSvg, paintIcon, defineIcon } from './chunk-Y4YIYNUZ.js';
2
2
  import { definer, attachStyles, controlCss, emit } from './chunk-EY266IK3.js';
3
3
 
4
4
  // src/button.ts
@@ -34,7 +34,8 @@ var defineButton = definer("office-ui-button", () => {
34
34
  "pressed",
35
35
  "expanded",
36
36
  "icon-only",
37
- "title"
37
+ "title",
38
+ "keyshortcuts"
38
39
  ];
39
40
  button;
40
41
  svg;
@@ -84,6 +85,9 @@ var defineButton = definer("office-ui-button", () => {
84
85
  else this.button.removeAttribute("aria-label");
85
86
  this.button.title = this.getAttribute("title") ?? (iconOnly ? label : "");
86
87
  this.button.disabled = this.disabled;
88
+ const shortcuts = this.getAttribute("keyshortcuts");
89
+ if (shortcuts) this.button.setAttribute("aria-keyshortcuts", shortcuts);
90
+ else this.button.removeAttribute("aria-keyshortcuts");
87
91
  if (paintIcon(this.svg, this.getAttribute("icon"))) this.svg.setAttribute("data-painted", "");
88
92
  else this.svg.removeAttribute("data-painted");
89
93
  for (const attr of ["pressed", "expanded"]) {
@@ -648,7 +652,9 @@ var defineStatusBar = definer("office-ui-status-bar", () => {
648
652
  super();
649
653
  const root = this.attachShadow({ mode: "open" });
650
654
  attachStyles(root, controlCss(BAR_CSS));
651
- root.append(this.ownerDocument.createElement("slot"));
655
+ const end = this.ownerDocument.createElement("slot");
656
+ end.name = "end";
657
+ root.append(this.ownerDocument.createElement("slot"), end);
652
658
  }
653
659
  connectedCallback() {
654
660
  this.setAttribute("role", "group");
@@ -725,6 +731,300 @@ var defineStatusItem = definer("office-ui-status-item", () => {
725
731
  return OfficeUiStatusItem;
726
732
  });
727
733
 
734
+ // src/tab-strip.ts
735
+ var CSS4 = `
736
+ :host { display: flex; align-items: stretch; min-width: 0; min-height: 28px; gap: 2px; padding: 0 6px;
737
+ background: var(--office-surface, #f3f4f6); color: var(--office-muted-foreground, #6b7280);
738
+ border-top: 1px solid var(--office-border, #d1d5db); font-size: 12px;
739
+ font-family: var(--office-font, system-ui, sans-serif); }
740
+ .step { flex: none; display: inline-grid; place-items: center; width: var(--office-target-size, 26px);
741
+ min-height: var(--office-target-size, 26px);
742
+ padding: 0; border: 0; background: transparent; color: inherit; cursor: pointer; }
743
+ .step:disabled { opacity: .4; cursor: default; }
744
+ .list { display: flex; align-items: stretch; min-width: 0; overflow-x: auto; scrollbar-width: thin; }
745
+ .tab { flex: none; max-width: 220px; min-height: var(--office-target-size, 26px); margin-top: -1px;
746
+ padding: 2px 14px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
747
+ border: 1px solid transparent; border-top: 0; border-radius: 0 0 var(--office-radius, 4px) var(--office-radius, 4px);
748
+ background: transparent; color: inherit; font: inherit; cursor: pointer; }
749
+ .tab:hover { color: var(--office-foreground, #1f2937); }
750
+ .tab[aria-selected="true"] { border-color: var(--office-border, #d1d5db); background: var(--office-background, #fff);
751
+ color: var(--office-accent, #2563eb); font-weight: 600; box-shadow: inset 0 2px 0 var(--office-accent, #2563eb); }
752
+ button:focus-visible { outline: 2px solid var(--office-ring, #2563eb); outline-offset: -2px; }
753
+ svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5;
754
+ stroke-linecap: round; stroke-linejoin: round; }
755
+ @media (forced-colors: active) {
756
+ :host { border-top-color: CanvasText; }
757
+ .tab[aria-selected="true"] { background: Highlight; color: HighlightText; forced-color-adjust: none; }
758
+ }
759
+ `;
760
+ var defineTabStrip = definer("office-ui-tab-strip", () => {
761
+ class OfficeUiTabStrip extends HTMLElement {
762
+ static observedAttributes = ["label", "disabled", "selected", "previous-label", "next-label"];
763
+ items = [];
764
+ list;
765
+ previous;
766
+ next;
767
+ constructor() {
768
+ super();
769
+ const doc = this.ownerDocument;
770
+ const root = this.attachShadow({ mode: "open" });
771
+ attachStyles(root, controlCss(CSS4));
772
+ const step = (name, icon) => {
773
+ const button = doc.createElement("button");
774
+ button.type = "button";
775
+ button.className = "step";
776
+ button.setAttribute("aria-label", name);
777
+ button.title = name;
778
+ const svg = createIconSvg(doc);
779
+ paintIcon(svg, icon);
780
+ button.append(svg);
781
+ return button;
782
+ };
783
+ this.previous = step("Previous", "chevronLeft");
784
+ this.next = step("Next", "chevronRight");
785
+ this.list = doc.createElement("div");
786
+ this.list.className = "list";
787
+ this.list.setAttribute("role", "tablist");
788
+ root.append(this.previous, this.next, this.list);
789
+ this.previous.addEventListener("click", () => this.move(-1, false));
790
+ this.next.addEventListener("click", () => this.move(1, false));
791
+ this.list.addEventListener("click", (event) => {
792
+ const tab = event.target.closest(".tab");
793
+ if (tab?.dataset.id !== void 0) this.choose(tab.dataset.id, false);
794
+ });
795
+ this.list.addEventListener("keydown", (event) => this.onKey(event));
796
+ }
797
+ connectedCallback() {
798
+ this.render();
799
+ }
800
+ attributeChangedCallback(name) {
801
+ if (name === "selected") this.sync();
802
+ else this.render();
803
+ }
804
+ get tabs() {
805
+ return this.items.map((tab) => ({ ...tab }));
806
+ }
807
+ set tabs(value) {
808
+ this.items = (Array.isArray(value) ? value : []).filter((tab) => tab && typeof tab.id === "string").map((tab) => ({
809
+ id: tab.id,
810
+ label: String(tab.label ?? ""),
811
+ ...tab.title === void 0 ? {} : { title: String(tab.title) }
812
+ }));
813
+ this.render();
814
+ }
815
+ get selected() {
816
+ return this.getAttribute("selected") ?? "";
817
+ }
818
+ set selected(id) {
819
+ this.setAttribute("selected", String(id));
820
+ }
821
+ index() {
822
+ return this.items.findIndex((tab) => tab.id === this.selected);
823
+ }
824
+ move(delta, focus) {
825
+ const index = this.index();
826
+ const target = this.items[Math.max(0, Math.min(this.items.length - 1, index + delta))];
827
+ if (target) this.choose(target.id, focus);
828
+ }
829
+ choose(id, focus) {
830
+ if (this.hasAttribute("disabled")) return;
831
+ if (id !== this.selected && emit(this, "office-tab-select", { id }, true)) this.selected = id;
832
+ if (focus) this.tabButton(this.selected)?.focus();
833
+ }
834
+ tabButton(id) {
835
+ return [...this.list.querySelectorAll(".tab")].find(
836
+ (tab) => tab.dataset.id === id
837
+ );
838
+ }
839
+ onKey(event) {
840
+ const keys = {
841
+ ArrowLeft: -1,
842
+ ArrowRight: 1,
843
+ ArrowUp: -1,
844
+ ArrowDown: 1
845
+ };
846
+ if (event.key === "Home" || event.key === "End") {
847
+ event.preventDefault();
848
+ const target = event.key === "Home" ? this.items[0] : this.items[this.items.length - 1];
849
+ if (target) this.choose(target.id, true);
850
+ } else if (keys[event.key] !== void 0) {
851
+ event.preventDefault();
852
+ this.move(keys[event.key], true);
853
+ }
854
+ }
855
+ render() {
856
+ const doc = this.ownerDocument;
857
+ this.list.setAttribute("aria-label", this.getAttribute("label") ?? "Tabs");
858
+ for (const [button, name, fallback] of [
859
+ [this.previous, "previous-label", "Previous"],
860
+ [this.next, "next-label", "Next"]
861
+ ]) {
862
+ const label = this.getAttribute(name) || fallback;
863
+ button.setAttribute("aria-label", label);
864
+ button.title = label;
865
+ }
866
+ this.list.replaceChildren(
867
+ ...this.items.map((item) => {
868
+ const tab = doc.createElement("button");
869
+ tab.type = "button";
870
+ tab.className = "tab";
871
+ tab.setAttribute("role", "tab");
872
+ tab.dataset.id = item.id;
873
+ tab.textContent = item.label;
874
+ tab.title = item.title ?? item.label;
875
+ return tab;
876
+ })
877
+ );
878
+ this.sync();
879
+ }
880
+ sync() {
881
+ const disabled = this.hasAttribute("disabled");
882
+ const index = this.index();
883
+ for (const tab of this.list.querySelectorAll(".tab")) {
884
+ const selected = tab.dataset.id === this.selected;
885
+ tab.setAttribute("aria-selected", String(selected));
886
+ tab.tabIndex = selected || index < 0 && tab === this.list.firstElementChild ? 0 : -1;
887
+ tab.disabled = disabled;
888
+ if (selected) tab.scrollIntoView?.({ block: "nearest", inline: "nearest" });
889
+ }
890
+ this.previous.disabled = disabled || index <= 0;
891
+ this.next.disabled = disabled || index < 0 || index >= this.items.length - 1;
892
+ }
893
+ }
894
+ return OfficeUiTabStrip;
895
+ });
896
+
897
+ // src/zoom-slider.ts
898
+ var CSS5 = `
899
+ :host { display: inline-flex; align-items: center; gap: 2px; font-size: 11px;
900
+ color: var(--office-foreground, #1f2937); font-family: var(--office-font, system-ui, sans-serif); }
901
+ button { box-sizing: border-box; display: inline-grid; place-items: center; min-width: var(--office-target-size, 24px);
902
+ min-height: var(--office-target-size, 24px); padding: 0; border: 1px solid transparent;
903
+ border-radius: var(--office-radius, 4px); background: transparent; color: inherit; font: inherit;
904
+ font-size: 15px; line-height: 1; cursor: pointer; }
905
+ button:hover:not(:disabled) { background: var(--office-background, #fff); }
906
+ button:focus-visible, input:focus-visible { outline: 2px solid var(--office-ring, #2563eb); outline-offset: 1px; }
907
+ button:disabled, input:disabled { opacity: .5; cursor: not-allowed; }
908
+ input { width: var(--office-zoom-slider-width, 110px); min-width: 48px; margin: 0 2px;
909
+ accent-color: var(--office-accent, #2563eb); }
910
+ output { min-width: 40px; text-align: center; font-variant-numeric: tabular-nums; }
911
+ svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5;
912
+ stroke-linecap: round; stroke-linejoin: round; }
913
+ :host(:not([fit])) .fit { display: none; }
914
+ @media (pointer: coarse), (max-width: 767px) { input { flex: 1; } }
915
+ @media (forced-colors: active) { button { color: ButtonText; } button:disabled { color: GrayText; } }
916
+ `;
917
+ var clampTo = (value, min, max) => Math.min(max, Math.max(min, Math.round(value)));
918
+ var number = (value, fallback) => {
919
+ const parsed = Number(value);
920
+ return value !== null && Number.isFinite(parsed) ? parsed : fallback;
921
+ };
922
+ var defineZoomSlider = definer("office-ui-zoom-slider", () => {
923
+ class OfficeUiZoomSlider extends HTMLElement {
924
+ static observedAttributes = ["value", "min", "max", "disabled", "fit", "label"];
925
+ out;
926
+ in;
927
+ range;
928
+ output;
929
+ fit;
930
+ constructor() {
931
+ super();
932
+ const doc = this.ownerDocument;
933
+ const root = this.attachShadow({ mode: "open" });
934
+ attachStyles(root, controlCss(CSS5));
935
+ const button = (name, text) => {
936
+ const element = doc.createElement("button");
937
+ element.type = "button";
938
+ element.setAttribute("aria-label", name);
939
+ element.title = name;
940
+ element.textContent = text;
941
+ return element;
942
+ };
943
+ this.out = button("Zoom out", "\u2212");
944
+ this.in = button("Zoom in", "+");
945
+ this.range = doc.createElement("input");
946
+ this.range.type = "range";
947
+ this.range.setAttribute("aria-label", "Zoom");
948
+ this.output = doc.createElement("output");
949
+ this.fit = button("Fit", "");
950
+ this.fit.className = "fit";
951
+ const svg = createIconSvg(doc);
952
+ paintIcon(svg, "fitPage");
953
+ this.fit.append(svg);
954
+ root.append(this.out, this.range, this.in, this.output, this.fit);
955
+ this.out.addEventListener("click", () => this.stepBy(-1));
956
+ this.in.addEventListener("click", () => this.stepBy(1));
957
+ this.range.addEventListener("input", () => this.commit(this.range.valueAsNumber, "input"));
958
+ this.range.addEventListener(
959
+ "change",
960
+ () => this.dispatchEvent(new Event("change", { bubbles: true, composed: true }))
961
+ );
962
+ this.fit.addEventListener(
963
+ "click",
964
+ () => emit(this, "office-command", { command: "zoom-fit" })
965
+ );
966
+ }
967
+ connectedCallback() {
968
+ this.setAttribute("role", "group");
969
+ this.sync();
970
+ }
971
+ attributeChangedCallback() {
972
+ this.sync();
973
+ }
974
+ get min() {
975
+ return number(this.getAttribute("min"), 10);
976
+ }
977
+ get max() {
978
+ return Math.max(this.min, number(this.getAttribute("max"), 400));
979
+ }
980
+ get value() {
981
+ return clampTo(number(this.getAttribute("value"), 100), this.min, this.max);
982
+ }
983
+ set value(next) {
984
+ this.setAttribute("value", String(clampTo(Number(next) || 0, this.min, this.max)));
985
+ }
986
+ get disabled() {
987
+ return this.hasAttribute("disabled");
988
+ }
989
+ set disabled(value) {
990
+ this.toggleAttribute("disabled", Boolean(value));
991
+ }
992
+ /** Office buttons snap to the next multiple of `step`, so 67% steps to 70%, not 77%. */
993
+ stepBy(direction) {
994
+ const step = Math.max(1, number(this.getAttribute("step"), 10));
995
+ const current = this.value;
996
+ const next = direction > 0 ? Math.floor(current / step) * step + step : Math.ceil(current / step) * step - step;
997
+ this.commit(next, "change");
998
+ }
999
+ commit(next, type) {
1000
+ if (this.disabled) return;
1001
+ const value = clampTo(next, this.min, this.max);
1002
+ if (value === this.value && type === "change") return;
1003
+ this.value = value;
1004
+ this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
1005
+ if (type === "change")
1006
+ this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
1007
+ }
1008
+ sync() {
1009
+ const { min, max, value, disabled } = this;
1010
+ this.setAttribute("aria-label", this.getAttribute("label") ?? "Zoom");
1011
+ this.range.min = String(min);
1012
+ this.range.max = String(max);
1013
+ this.range.value = String(value);
1014
+ this.range.setAttribute("aria-valuetext", `${value}%`);
1015
+ this.output.value = `${value}%`;
1016
+ this.out.disabled = disabled || value <= min;
1017
+ this.in.disabled = disabled || value >= max;
1018
+ this.range.disabled = disabled;
1019
+ const fit = this.getAttribute("fit");
1020
+ this.fit.disabled = disabled;
1021
+ this.fit.setAttribute("aria-label", fit || "Fit");
1022
+ this.fit.title = fit || "Fit";
1023
+ }
1024
+ }
1025
+ return OfficeUiZoomSlider;
1026
+ });
1027
+
728
1028
  // src/controls.ts
729
1029
  var CONTROL_DEFINERS = [
730
1030
  defineIcon,
@@ -736,10 +1036,12 @@ var CONTROL_DEFINERS = [
736
1036
  defineToolbar,
737
1037
  defineDialog,
738
1038
  defineStatusBar,
739
- defineStatusItem
1039
+ defineStatusItem,
1040
+ defineZoomSlider,
1041
+ defineTabStrip
740
1042
  ];
741
1043
  function registerControls(registry) {
742
1044
  for (const define of CONTROL_DEFINERS) define(registry);
743
1045
  }
744
1046
 
745
- export { CONTROL_DEFINERS, defineButton, defineCheckbox, defineDialog, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineToolbar, registerControls };
1047
+ export { CONTROL_DEFINERS, defineButton, defineCheckbox, defineDialog, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, registerControls };
@@ -18,7 +18,17 @@ var BUILT_IN = {
18
18
  settings: "M3 5h14M6 10h8M8 15h4M6 3v4M12 8v4M10 13v4",
19
19
  clock: "M3 10a7 7 0 1 0 14 0 7 7 0 1 0-14 0M10 6v4l3 2",
20
20
  reset: "M4 10a6 6 0 1 1 1.8 4.2M4 6v4h4",
21
- search: "M3 8.5a5.5 5.5 0 1 0 11 0 5.5 5.5 0 1 0-11 0M13 13l4 4"
21
+ search: "M3 8.5a5.5 5.5 0 1 0 11 0 5.5 5.5 0 1 0-11 0M13 13l4 4",
22
+ undo: "M7.5 11.5 3.5 7.5l4-4M3.5 7.5h9a4 4 0 0 1 0 8H10",
23
+ redo: "m12.5 11.5 4-4-4-4M16.5 7.5h-9a4 4 0 0 0 0 8H10",
24
+ pointer: "m5 2.5v13.5l3.6-3.6 2.4 5.1 2.1-1-2.4-5H15.5Z",
25
+ rectangle: "M3 5.5h14v9H3Z",
26
+ fullscreen: "M3 7.5V3h4.5M12.5 3H17v4.5M17 12.5V17h-4.5M7.5 17H3v-4.5",
27
+ grid: "M3 3h14v14H3ZM7.7 3v14M12.3 3v14M3 7.7h14M3 12.3h14",
28
+ fitPage: "M6.5 6.5h7v7h-7ZM2.5 6V2.5H6M14 2.5h3.5V6M17.5 14v3.5H14M6 17.5H2.5V14",
29
+ pageWidth: "M5.5 3.5h9v13h-9ZM1.5 10h17M4 8l-2.5 2L4 12M16 8l2.5 2-2.5 2",
30
+ zoomIn: "M3 8.5a5.5 5.5 0 1 0 11 0 5.5 5.5 0 1 0-11 0M13 13l4 4M6 8.5h5M8.5 6v5",
31
+ zoomOut: "M3 8.5a5.5 5.5 0 1 0 11 0 5.5 5.5 0 1 0-11 0M13 13l4 4M6 8.5h5"
22
32
  };
23
33
  for (const [name, d] of Object.entries(BUILT_IN)) registry.set(name, { d });
24
34
  function registerIcon(name, icon) {
@@ -5,12 +5,15 @@ import { defineIcon } from './icons.js';
5
5
  import { defineRibbonGroup, defineToolbar } from './ribbon.js';
6
6
  import { defineSelect } from './select.js';
7
7
  import { defineStatusBar, defineStatusItem } from './status-bar.js';
8
- export { defineButton, defineCheckbox, defineDialog, defineIcon, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineToolbar, };
8
+ import { defineTabStrip } from './tab-strip.js';
9
+ import { defineZoomSlider } from './zoom-slider.js';
10
+ export { defineButton, defineCheckbox, defineDialog, defineIcon, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, };
9
11
  export type { OfficeCommandEvent } from './button.js';
10
12
  export type { OfficeDialogCloseEvent, OfficeDialogCloseReason } from './dialog.js';
11
13
  export type { OfficeSelectOption } from './select.js';
12
14
  export type { OfficeStatusActivateEvent } from './status-bar.js';
15
+ export type { OfficeTab, OfficeTabSelectEvent } from './tab-strip.js';
13
16
  /** Every control of this entry. */
14
- 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];
17
+ 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];
15
18
  /** Idempotent, SSR-safe (no-op without a DOM). */
16
19
  export declare function registerControls(registry?: CustomElementRegistry): void;
package/dist/controls.js CHANGED
@@ -1,3 +1,3 @@
1
- export { CONTROL_DEFINERS, defineButton, defineCheckbox, defineDialog, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineToolbar, registerControls } from './chunk-5UKGBJXE.js';
2
- export { defineIcon } from './chunk-7MCSBDSU.js';
1
+ export { CONTROL_DEFINERS, defineButton, defineCheckbox, defineDialog, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, registerControls } from './chunk-Q2FH3CZQ.js';
2
+ export { defineIcon } from './chunk-Y4YIYNUZ.js';
3
3
  import './chunk-EY266IK3.js';
package/dist/icons.js CHANGED
@@ -1,2 +1,2 @@
1
- export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-7MCSBDSU.js';
1
+ export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-Y4YIYNUZ.js';
2
2
  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-toolbar", "office-ui-dialog", "office-ui-status-bar", "office-ui-status-item", "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-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"];
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,8 +1,8 @@
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-5UKGBJXE.js';
4
- export { CONTROL_DEFINERS, defineButton, defineCheckbox, defineDialog, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineToolbar, registerControls } from './chunk-5UKGBJXE.js';
5
- export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-7MCSBDSU.js';
3
+ import { registerControls } from './chunk-Q2FH3CZQ.js';
4
+ export { CONTROL_DEFINERS, defineButton, defineCheckbox, defineDialog, defineRibbonGroup, defineSelect, defineStatusBar, defineStatusItem, defineSwitch, defineTabStrip, defineToolbar, defineZoomSlider, registerControls } from './chunk-Q2FH3CZQ.js';
5
+ export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-Y4YIYNUZ.js';
6
6
  import { definePresence } from './chunk-RLPL7Q6J.js';
7
7
  export { definePresence, initialsOf, participantsFromAwareness } from './chunk-RLPL7Q6J.js';
8
8
  import { defineSmartArt } from './chunk-KJV76AB7.js';
@@ -21,6 +21,8 @@ var OFFICE_UI_TAGS = [
21
21
  "office-ui-dialog",
22
22
  "office-ui-status-bar",
23
23
  "office-ui-status-item",
24
+ "office-ui-zoom-slider",
25
+ "office-ui-tab-strip",
24
26
  "office-ui-presence",
25
27
  "office-ui-smartart"
26
28
  ];
@@ -1,7 +1,10 @@
1
1
  export type OfficeStatusActivateEvent = CustomEvent<{
2
2
  id: string;
3
3
  }>;
4
- /** Status bar container: `role="group"` named by its `label` attribute (default "Status"). */
4
+ /**
5
+ * Status bar container: `role="group"` named by its `label` attribute (default "Status").
6
+ * Children with `slot="end"` are pushed to the trailing edge (zoom, view switches).
7
+ */
5
8
  export declare const defineStatusBar: import("./registry.js").Definer;
6
9
  /**
7
10
  * One status entry: `label` and `value` attributes (textual, so assistive tech reads both).
@@ -0,0 +1,18 @@
1
+ export interface OfficeTab {
2
+ id: string;
3
+ label: string;
4
+ /** Tooltip; defaults to the label. */
5
+ title?: string;
6
+ }
7
+ export type OfficeTabSelectEvent = CustomEvent<{
8
+ id: string;
9
+ }>;
10
+ /**
11
+ * Bottom document tabs (Visio pages, Excel sheets). Set `tabs` (`OfficeTab[]`) and `selected`
12
+ * (a tab id). Labels are inserted as text. The previous/next buttons and arrow, Home and End
13
+ * keys move the selection; the tab list uses a roving tabindex. User selection emits
14
+ * `office-tab-select` `{ id }` (cancelable); setting properties never emits.
15
+ * Attributes: `label` (tab list name, default "Tabs"), `previous-label` and `next-label`
16
+ * (step button names, default "Previous" and "Next"), `disabled`.
17
+ */
18
+ export declare const defineTabStrip: import("./registry.js").Definer;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Office status-bar zoom: zoom out, slider, zoom in, percentage and an optional fit button.
3
+ * Attributes: `value` (percent, default 100), `min` (10), `max` (400), `step` (10, for the
4
+ * buttons), `disabled`, `fit` (shows the fit button; its value is the button's name, e.g.
5
+ * "Fit page to current window"), `label` (group name, default "Zoom").
6
+ * User changes emit native `input` (slider drag) and `change` events with `value` updated;
7
+ * the fit button emits `office-command` `{ command: 'zoom-fit' }`. Setting `value` never emits.
8
+ */
9
+ export declare const defineZoomSlider: import("./registry.js").Definer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ooxml-ui",
3
- "version": "0.1.11",
3
+ "version": "0.2.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",
@@ -70,7 +70,7 @@
70
70
  "test:package": "node scripts/package-smoke.mjs"
71
71
  },
72
72
  "dependencies": {
73
- "ooxml-core": "^0.11.0"
73
+ "ooxml-core": "^0.12.0"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/jsdom": "^21.1.7",