ooxml-ui 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/button.d.ts CHANGED
@@ -3,9 +3,12 @@ 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`, `keyshortcuts`
7
- * (forwarded as aria-keyshortcuts, e.g. `Control+Z`).
8
- * Activation (pointer, Enter, Space) emits one bubbling, composed `office-command`
9
- * event with `{ command }`; the host decides what it does. Nothing happens when disabled.
6
+ * `expanded` (aria-expanded), `icon-only`, `variant="stacked"`, `size="large|small"` (ribbon
7
+ * large and small commands), `tall` (with `icon-only`: a tool tile), `active`, `badge`, `caret`
8
+ * (a trailing menu chevron), `title`, `keyshortcuts` (forwarded as aria-keyshortcuts).
9
+ * Activation (pointer, Enter, Space) emits one bubbling, composed `office-command` `{ command }`;
10
+ * Enter and Space never reach the host's own key handlers. Nothing happens when disabled.
11
+ * Static `requestEvent`, `idAttribute` and `detailKey`, and the `iconName()` and `showsCaret()`
12
+ * methods, let a product subclass keep its published contract and glyph set.
10
13
  */
11
14
  export declare const defineButton: import("./registry.js").Definer;
@@ -128,6 +128,28 @@ var OFFICE_TOKENS = {
128
128
  "--office-ribbon-tab-height": "35px",
129
129
  "--office-ribbon-group-gap": "2px",
130
130
  "--office-ribbon-group-height": "78px",
131
+ "--office-ribbon-group-padding": "4px 8px 2px",
132
+ "--office-ribbon-separator-inset": "0px",
133
+ "--office-ribbon-separator": "color-mix(in srgb, var(--office-border, #d1d5db) 60%, transparent)",
134
+ "--office-ribbon-caption-size": "10px",
135
+ "--office-ribbon-caption-padding": "2px 0 0",
136
+ "--office-launcher-inset-end": "-6px",
137
+ "--office-launcher-inset-bottom": "-1px",
138
+ "--office-ribbon-compact-row-max": "36px",
139
+ "--office-ribbon-stack-max-height": "80px",
140
+ "--office-ribbon-collapse-x": "8px",
141
+ "--office-ribbon-collapse-y": "120px",
142
+ "--office-command-icon-color": "currentColor",
143
+ "--office-command-large-height": "66px",
144
+ "--office-command-large-min-width": "48px",
145
+ "--office-command-large-max-width": "80px",
146
+ "--office-command-large-icon": "32px",
147
+ "--office-command-small-icon": "16px",
148
+ "--office-command-tall-height": "62px",
149
+ "--office-command-tall-min-width": "40px",
150
+ "--office-command-tall-icon": "30px",
151
+ "--office-command-active": "color-mix(in srgb, var(--office-accent, #2563eb) 15%, transparent)",
152
+ "--office-collapsed-group-min-width": "56px",
131
153
  "--office-ribbon-caption-line-height": "12px",
132
154
  "--office-ribbon-row-height": "22px",
133
155
  "--office-ribbon-file-min-width": "52px",
@@ -1,5 +1,5 @@
1
- import { definer, attachStyles, controlCss, emit } from './chunk-3BHP4IFP.js';
2
- import { tok } from './chunk-BDJW2KDI.js';
1
+ import { definer, attachStyles, controlCss, emit } from './chunk-NDEB6EBU.js';
2
+ import { tok } from './chunk-4MT4OIAJ.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-BDJW2KDI.js';
1
+ import { OFFICE_TOKENS, TOUCH } from './chunk-4MT4OIAJ.js';
2
2
 
3
3
  // src/theme.ts
4
4
  var THEME_STYLE_ID = "office-ui-theme";
@@ -1,4 +1,4 @@
1
- import { TOUCH, tok } from './chunk-BDJW2KDI.js';
1
+ import { TOUCH, tok } from './chunk-4MT4OIAJ.js';
2
2
 
3
3
  // src/registry.ts
4
4
  var CONTRACT_REVISION = 1;
@@ -1,4 +1,4 @@
1
- import { definer, attachStyles, controlCss } from './chunk-3BHP4IFP.js';
1
+ import { definer, attachStyles, controlCss } from './chunk-NDEB6EBU.js';
2
2
 
3
3
  // src/icons.ts
4
4
  var registry = /* @__PURE__ */ new Map();
@@ -1,5 +1,5 @@
1
- import { definer, attachStyles, controlCss, emit } from './chunk-3BHP4IFP.js';
2
- import { tok } from './chunk-BDJW2KDI.js';
1
+ import { definer, attachStyles, controlCss, emit } from './chunk-NDEB6EBU.js';
2
+ import { tok } from './chunk-4MT4OIAJ.js';
3
3
 
4
4
  // src/presence.ts
5
5
  function participantsFromAwareness(states, selfId) {
@@ -1,7 +1,7 @@
1
- import { createIconSvg, paintIcon, defineIcon } from './chunk-WNSUNQIY.js';
2
- import { initialsOf } from './chunk-WKLVWPI7.js';
3
- import { definer, attachStyles, controlCss, emit } from './chunk-3BHP4IFP.js';
4
- import { tok, FOCUS_RING, OFFICE_TOKENS } from './chunk-BDJW2KDI.js';
1
+ import { createIconSvg, paintIcon, defineIcon } from './chunk-VUXNXH4Q.js';
2
+ import { initialsOf } from './chunk-VYF7LGXZ.js';
3
+ import { definer, attachStyles, controlCss, emit } from './chunk-NDEB6EBU.js';
4
+ import { tok, FOCUS_RING, OFFICE_TOKENS } from './chunk-4MT4OIAJ.js';
5
5
 
6
6
  // src/chrome-controls.ts
7
7
  var ICON = `svg { flex: none; width: ${tok("--office-icon-size")}; height: ${tok("--office-icon-size")}; fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")};
@@ -1770,56 +1770,114 @@ var defineAccount = definer("office-ui-account", () => {
1770
1770
  // src/button.ts
1771
1771
  var CSS10 = `
1772
1772
  :host { display: inline-flex; vertical-align: middle; }
1773
- button { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
1773
+ button { box-sizing: border-box; position: relative; display: inline-flex; align-items: center; justify-content: center;
1774
1774
  gap: ${tok("--office-space-1-5")}; min-width: ${tok("--office-target-size")}; min-height: ${tok("--office-target-size")};
1775
1775
  padding: 0 ${tok("--office-space-2")}; border: ${tok("--office-border-width")} solid transparent; border-radius: ${tok("--office-radius")};
1776
1776
  background: transparent; color: ${tok("--office-foreground")}; font: inherit; font-size: ${tok("--office-font-size-sm")};
1777
1777
  cursor: pointer; white-space: nowrap; }
1778
1778
  :host([variant="stacked"]) button { flex-direction: column; padding: ${tok("--office-space-1")} ${tok("--office-space-2")}; }
1779
1779
  button:hover:not(:disabled) { background: ${tok("--office-surface")}; }
1780
+ button:active:not(:disabled) { background: ${tok("--office-selected")}; }
1780
1781
  button:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; outline-offset: calc(${tok("--office-focus-offset")} / 2); }
1781
1782
  button[aria-pressed="true"], button[aria-expanded="true"] { border-color: ${tok("--office-accent")};
1782
1783
  background: ${tok("--office-surface")}; }
1784
+ :host([active]) button { background: ${tok("--office-command-active")}; color: ${tok("--office-accent")}; }
1783
1785
  button:disabled { opacity: .5; cursor: not-allowed; }
1784
- svg { width: ${tok("--office-icon-size-ml")}; height: ${tok("--office-icon-size-ml")}; fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")};
1785
- stroke-linecap: round; stroke-linejoin: round; }
1786
- svg:not([data-painted]) { display: none; }
1786
+ svg { width: ${tok("--office-icon-size-ml")}; height: ${tok("--office-icon-size-ml")}; flex: none; color: ${tok("--office-command-icon-color")};
1787
+ fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; }
1788
+ svg.glyph:not([data-painted]) { display: none; }
1789
+ .label[hidden], .caret[hidden], .badge[hidden] { display: none; }
1790
+ /* Office trails a menu chevron after the last line of the label. */
1791
+ .caret { display: inline-block; width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")};
1792
+ margin-inline-start: ${tok("--office-space-0")}; vertical-align: middle; color: ${tok("--office-muted-foreground")}; }
1793
+ .badge { position: absolute; top: 0; inset-inline-end: 0; padding: 0 ${tok("--office-space-0")}; border-radius: ${tok("--office-radius-full")};
1794
+ background: ${tok("--office-accent")}; color: ${tok("--office-accent-foreground")}; font-size: ${tok("--office-font-size-3xs")}; }
1795
+ /* Large ribbon command: a big glyph above a two-line label. */
1796
+ :host([size="large"]) button { flex-direction: column; justify-content: flex-start; gap: ${tok("--office-space-0")};
1797
+ min-width: ${tok("--office-command-large-min-width")}; max-width: ${tok("--office-command-large-max-width")};
1798
+ height: ${tok("--office-command-large-height")}; padding: ${tok("--office-space-0")} ${tok("--office-space-1")};
1799
+ border: 0; white-space: normal; text-align: center; line-height: ${tok("--office-line-height-tight")}; }
1800
+ :host([size="large"]) svg.glyph { width: ${tok("--office-command-large-icon")}; height: ${tok("--office-command-large-icon")}; }
1801
+ /* Small ribbon command: icon and label in one row. */
1802
+ :host([size="small"]) button { justify-content: flex-start; min-width: 0; height: ${tok("--office-control-height")};
1803
+ min-height: ${tok("--office-control-height")}; padding: 0 ${tok("--office-space-1-5")}; border: 0; text-align: start; }
1804
+ :host([size="small"]) svg.glyph { width: ${tok("--office-command-small-icon")}; height: ${tok("--office-command-small-icon")}; }
1805
+ /* Tall, label-less tool tiles (pens, erasers). */
1806
+ :host([icon-only][tall]) button { min-width: ${tok("--office-command-tall-min-width")}; height: ${tok("--office-command-tall-height")}; padding: ${tok("--office-space-1")}; }
1807
+ :host([icon-only][tall]) svg.glyph { width: ${tok("--office-command-tall-icon")}; height: ${tok("--office-command-tall-icon")}; }
1808
+ :host([icon-only][tall][active]) button { box-shadow: inset 0 0 0 ${tok("--office-border-width")} ${tok("--office-accent")}; }
1809
+ @media (pointer: coarse), (max-width: 767px) {
1810
+ :host([size="small"]) button { min-height: ${tok("--office-target-size-touch")}; }
1811
+ :host([icon-only]) button { min-width: ${tok("--office-target-size-touch")}; }
1812
+ }
1787
1813
  @media (forced-colors: active) {
1788
1814
  button { border-color: ButtonText; color: ButtonText; background: ButtonFace; }
1815
+ svg { color: ButtonText; }
1789
1816
  button:disabled { color: GrayText; border-color: GrayText; }
1790
- button[aria-pressed="true"], button[aria-expanded="true"] { background: Highlight; color: HighlightText; }
1817
+ button[aria-pressed="true"], button[aria-expanded="true"], :host([active]) button { background: Highlight; color: HighlightText; }
1791
1818
  }
1792
1819
  `;
1793
1820
  var defineButton = definer("office-ui-button", () => {
1794
1821
  class OfficeUiButton extends HTMLElement {
1795
- static observedAttributes = [
1796
- "label",
1797
- "icon",
1798
- "command",
1799
- "disabled",
1800
- "pressed",
1801
- "expanded",
1802
- "icon-only",
1803
- "title",
1804
- "keyshortcuts"
1805
- ];
1822
+ static requestEvent = "office-command";
1823
+ static idAttribute = "command";
1824
+ static detailKey = "command";
1825
+ static get observedAttributes() {
1826
+ return [
1827
+ "label",
1828
+ "icon",
1829
+ "command",
1830
+ "disabled",
1831
+ "pressed",
1832
+ "expanded",
1833
+ "icon-only",
1834
+ "title",
1835
+ "keyshortcuts",
1836
+ "badge",
1837
+ "caret",
1838
+ this.idAttribute
1839
+ ];
1840
+ }
1806
1841
  button;
1807
1842
  svg;
1808
1843
  text;
1844
+ caret;
1845
+ badge;
1809
1846
  constructor() {
1810
1847
  super();
1848
+ const doc = this.ownerDocument;
1811
1849
  const root = this.attachShadow({ mode: "open", delegatesFocus: true });
1812
1850
  attachStyles(root, controlCss(CSS10));
1813
- this.button = this.ownerDocument.createElement("button");
1851
+ this.button = doc.createElement("button");
1814
1852
  this.button.type = "button";
1815
1853
  this.button.setAttribute("part", "button");
1816
- this.svg = createIconSvg(this.ownerDocument);
1817
- this.text = this.ownerDocument.createElement("span");
1818
- this.button.append(this.svg, this.text);
1854
+ this.svg = createIconSvg(doc);
1855
+ this.svg.classList.add("glyph");
1856
+ this.text = doc.createElement("span");
1857
+ this.text.className = "label";
1858
+ this.caret = createIconSvg(doc);
1859
+ this.caret.classList.add("caret");
1860
+ paintIcon(this.caret, "chevronDown");
1861
+ this.badge = doc.createElement("span");
1862
+ this.badge.className = "badge";
1863
+ this.badge.setAttribute("aria-hidden", "true");
1864
+ this.button.append(this.svg, this.text, this.badge);
1819
1865
  root.append(this.button);
1866
+ this.button.addEventListener("keydown", (event) => {
1867
+ if ((event.key === " " || event.key === "Enter") && !event.ctrlKey && !event.metaKey && !event.altKey)
1868
+ event.stopPropagation();
1869
+ });
1820
1870
  this.button.addEventListener("click", () => {
1821
- const command = this.getAttribute("command");
1822
- if (!this.button.disabled && command) emit(this, "office-command", { command });
1871
+ const { requestEvent, idAttribute, detailKey } = this.constructor;
1872
+ const id = this.getAttribute(idAttribute);
1873
+ if (!this.button.disabled && id)
1874
+ this.dispatchEvent(
1875
+ new CustomEvent(requestEvent, {
1876
+ detail: { [detailKey]: id },
1877
+ bubbles: true,
1878
+ composed: true
1879
+ })
1880
+ );
1823
1881
  });
1824
1882
  }
1825
1883
  connectedCallback() {
@@ -1842,10 +1900,21 @@ var defineButton = definer("office-ui-button", () => {
1842
1900
  if (value === void 0) this.removeAttribute("pressed");
1843
1901
  else this.setAttribute("pressed", String(value));
1844
1902
  }
1903
+ /** The registered icon to draw; a product subclass may map names onto its glyph set. */
1904
+ iconName() {
1905
+ return this.getAttribute("icon");
1906
+ }
1907
+ /** Whether the trailing menu chevron shows. */
1908
+ showsCaret() {
1909
+ const caret = this.getAttribute("caret");
1910
+ return caret !== null && caret !== "false";
1911
+ }
1845
1912
  sync() {
1846
1913
  const label = this.getAttribute("label") ?? "";
1847
1914
  const iconOnly = this.hasAttribute("icon-only");
1848
1915
  this.text.textContent = label;
1916
+ this.text.append(this.caret);
1917
+ this.caret.toggleAttribute("hidden", !this.showsCaret());
1849
1918
  this.text.hidden = iconOnly;
1850
1919
  if (iconOnly && label) this.button.setAttribute("aria-label", label);
1851
1920
  else this.button.removeAttribute("aria-label");
@@ -1854,8 +1923,10 @@ var defineButton = definer("office-ui-button", () => {
1854
1923
  const shortcuts = this.getAttribute("keyshortcuts");
1855
1924
  if (shortcuts) this.button.setAttribute("aria-keyshortcuts", shortcuts);
1856
1925
  else this.button.removeAttribute("aria-keyshortcuts");
1857
- if (paintIcon(this.svg, this.getAttribute("icon"))) this.svg.setAttribute("data-painted", "");
1926
+ if (paintIcon(this.svg, this.iconName())) this.svg.setAttribute("data-painted", "");
1858
1927
  else this.svg.removeAttribute("data-painted");
1928
+ this.badge.textContent = this.getAttribute("badge") ?? "";
1929
+ this.badge.hidden = !this.badge.textContent;
1859
1930
  for (const attr of ["pressed", "expanded"]) {
1860
1931
  const value = this.getAttribute(attr);
1861
1932
  if (value === null) this.button.removeAttribute(`aria-${attr}`);
@@ -3037,41 +3108,90 @@ function text(doc, tag, content, className) {
3037
3108
  // src/ribbon.ts
3038
3109
  var GROUP_CSS = `
3039
3110
  :host { display: inline-flex; flex: none; align-self: stretch; }
3040
- .group { box-sizing: border-box; display: flex; flex-direction: column; justify-content: space-between;
3041
- min-height: ${tok("--office-ribbon-group-height")}; padding: ${tok("--office-space-1")} ${tok("--office-space-2")} ${tok("--office-space-0")};
3042
- border-inline-end: ${tok("--office-border-width")} solid color-mix(in srgb, ${tok("--office-border")} 60%, transparent); }
3043
- :host(:last-child) .group { border-inline-end: 0; }
3044
- .row { display: flex; align-items: flex-start; gap: ${tok("--office-space-1")}; }
3111
+ .group { position: relative; box-sizing: border-box; display: flex; flex-direction: column; justify-content: space-between;
3112
+ min-height: ${tok("--office-ribbon-group-height")}; padding: ${tok("--office-ribbon-group-padding")}; }
3113
+ /* Office draws a hairline between groups; a product may stop it short of the top and bottom. */
3114
+ .group::after { content: ''; position: absolute; top: ${tok("--office-ribbon-separator-inset")}; bottom: ${tok("--office-ribbon-separator-inset")};
3115
+ inset-inline-end: 0; width: ${tok("--office-border-width")}; background: ${tok("--office-ribbon-separator")}; }
3116
+ :host(:last-child) .group::after { content: none; }
3117
+ .row { display: flex; align-items: flex-start; gap: ${tok("--office-space-1")}; flex: 1; }
3118
+ /* One-line groups centre their commands on the whole group box. */
3119
+ :host([data-compact-row]) .row { align-items: center; }
3120
+ /* Several small drop-down galleries stack in columns of three. */
3121
+ :host([data-stack]) .row { flex-direction: column; align-items: stretch; align-content: flex-start; flex-wrap: wrap;
3122
+ max-height: ${tok("--office-ribbon-stack-max-height")}; gap: ${tok("--office-space-0")}; }
3045
3123
  ::slotted(*) { flex-shrink: 0; }
3046
- .foot { position: relative; display: flex; justify-content: center; padding-top: ${tok("--office-space-0")}; }
3047
- .caption { color: ${tok("--office-muted-foreground")}; font-size: ${tok("--office-font-size-2xs")};
3124
+ .foot { position: relative; display: flex; align-items: center; justify-content: center; padding: ${tok("--office-ribbon-caption-padding")}; }
3125
+ .caption { color: ${tok("--office-muted-foreground")}; font-size: ${tok("--office-ribbon-caption-size")};
3048
3126
  line-height: ${tok("--office-ribbon-caption-line-height")}; text-align: center; white-space: nowrap; }
3049
- .launcher { position: absolute; inset-inline-end: -6px; bottom: calc(-1 * ${tok("--office-space-px")}); display: inline-grid; place-items: center;
3050
- width: ${tok("--office-launcher-width")}; height: ${tok("--office-launcher-height")}; padding: 0; border: 0; border-radius: ${tok("--office-radius-xs")}; background: transparent;
3051
- color: ${tok("--office-muted-foreground")}; cursor: pointer; }
3052
- .launcher:hover:not(:disabled) { background: ${tok("--office-surface")}; }
3127
+ .launcher { position: absolute; inset-inline-end: ${tok("--office-launcher-inset-end")}; bottom: ${tok("--office-launcher-inset-bottom")}; display: inline-grid; place-items: center;
3128
+ width: ${tok("--office-launcher-width")}; height: ${tok("--office-launcher-height")}; padding: 0; border: 0; border-radius: ${tok("--office-radius-xs")};
3129
+ background: transparent; color: ${tok("--office-muted-foreground")}; cursor: pointer; }
3130
+ .launcher:hover:not(:disabled) { background: ${tok("--office-surface")}; color: ${tok("--office-foreground")}; }
3053
3131
  .launcher:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; }
3054
3132
  .launcher:disabled { opacity: .45; cursor: not-allowed; }
3055
3133
  .launcher[hidden] { display: none; }
3056
- .launcher svg { width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")}; fill: none; stroke: currentColor; stroke-width: ${tok("--office-icon-stroke")};
3057
- stroke-linecap: round; stroke-linejoin: round; }
3058
- @media (forced-colors: active) { .group { border-inline-end-color: CanvasText; } .caption { color: CanvasText; } }
3134
+ .launcher svg { width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")}; fill: none; stroke: currentColor;
3135
+ stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; }
3136
+ /* Collapsed (narrow window): the group is one button; its commands open in a popup. */
3137
+ .face { display: none; }
3138
+ :host([data-collapsed]) .face { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: ${tok("--office-space-0")};
3139
+ min-width: ${tok("--office-collapsed-group-min-width")}; height: ${tok("--office-command-large-height")}; padding: ${tok("--office-space-0")} ${tok("--office-space-1-5")};
3140
+ border: 0; border-radius: ${tok("--office-radius")}; background: transparent; color: ${tok("--office-foreground")}; font: inherit;
3141
+ font-size: ${tok("--office-font-size-sm")}; cursor: pointer; }
3142
+ :host([data-collapsed]) .face:hover, :host([data-open]) .face { background: ${tok("--office-selected")}; }
3143
+ .face:focus-visible { outline: ${tok("--office-focus-width")} solid ${tok("--office-ring")}; outline-offset: ${tok("--office-focus-offset")}; }
3144
+ .face svg { width: ${tok("--office-command-large-icon")}; height: ${tok("--office-command-large-icon")}; fill: none; stroke: currentColor;
3145
+ stroke-width: ${tok("--office-icon-stroke")}; stroke-linecap: round; stroke-linejoin: round; color: ${tok("--office-command-icon-color")}; }
3146
+ .face svg.chev { width: ${tok("--office-icon-size-xs")}; height: ${tok("--office-icon-size-xs")}; color: currentColor; }
3147
+ :host([data-collapsed]) .foot, :host([data-collapsed]) .row { display: none; }
3148
+ :host([data-collapsed][data-open]) .row { display: flex; flex-wrap: wrap; position: fixed; top: ${tok("--office-ribbon-collapse-y")};
3149
+ left: ${tok("--office-ribbon-collapse-x")}; z-index: ${tok("--office-z-popover")}; box-sizing: border-box;
3150
+ max-width: calc(100vw - 2 * ${tok("--office-space-2")}); padding: ${tok("--office-space-2")}; background: ${tok("--office-popover")};
3151
+ color: ${tok("--office-popover-foreground")}; border: ${tok("--office-border-width")} solid ${tok("--office-border")};
3152
+ border-radius: ${tok("--office-radius-md")}; box-shadow: ${tok("--office-shadow")}; }
3153
+ @media (forced-colors: active) {
3154
+ .group::after { background: CanvasText; } .caption, .launcher { color: CanvasText; }
3155
+ :host([data-collapsed][data-open]) .row { border-color: CanvasText; }
3156
+ }
3059
3157
  `;
3158
+ function tokenPx(el, name, fallback) {
3159
+ const value = el.ownerDocument.defaultView?.getComputedStyle(el).getPropertyValue(name).trim();
3160
+ const px = value?.endsWith("px") ? parseFloat(value) : NaN;
3161
+ return Number.isFinite(px) ? px : fallback;
3162
+ }
3060
3163
  var defineRibbonGroup = definer("office-ui-ribbon-group", () => {
3061
3164
  class OfficeUiRibbonGroup extends HTMLElement {
3062
- static observedAttributes = ["label", "launcher", "launcher-disabled"];
3165
+ static launcherEvent = "office-command";
3166
+ static collapseEvent = "office-ribbon-collapse-toggle";
3167
+ static observedAttributes = [
3168
+ "label",
3169
+ "launcher",
3170
+ "launcher-label",
3171
+ "launcher-disabled",
3172
+ "icon",
3173
+ "data-open"
3174
+ ];
3063
3175
  caption;
3064
3176
  launcher;
3177
+ face;
3178
+ faceIcon;
3179
+ faceLabel;
3180
+ slotEl;
3181
+ observer;
3065
3182
  constructor() {
3066
3183
  super();
3067
3184
  const doc = this.ownerDocument;
3068
3185
  const root = this.attachShadow({ mode: "open" });
3069
3186
  attachStyles(root, controlCss(GROUP_CSS));
3187
+ const config3 = () => this.constructor;
3070
3188
  const group = doc.createElement("div");
3071
3189
  group.className = "group";
3072
3190
  const row = doc.createElement("div");
3073
3191
  row.className = "row";
3074
- row.append(doc.createElement("slot"));
3192
+ this.slotEl = doc.createElement("slot");
3193
+ this.slotEl.addEventListener("slotchange", () => this.observe());
3194
+ row.append(this.slotEl);
3075
3195
  this.caption = doc.createElement("span");
3076
3196
  this.caption.className = "caption";
3077
3197
  this.launcher = doc.createElement("button");
@@ -3081,30 +3201,94 @@ var defineRibbonGroup = definer("office-ui-ribbon-group", () => {
3081
3201
  paintIcon(glyph, "launcher");
3082
3202
  this.launcher.append(glyph);
3083
3203
  this.launcher.addEventListener("click", () => {
3084
- const command = this.getAttribute("launcher");
3085
- if (command && !this.launcher.disabled) emit(this, "office-command", { command });
3204
+ if (this.launcher.disabled) return;
3205
+ const detail = this.launcherDetail();
3206
+ if (detail)
3207
+ this.dispatchEvent(
3208
+ new CustomEvent(config3().launcherEvent, { detail, bubbles: true, composed: true })
3209
+ );
3086
3210
  });
3087
3211
  const foot = doc.createElement("div");
3088
3212
  foot.className = "foot";
3089
3213
  foot.append(this.caption, this.launcher);
3090
- group.append(row, foot);
3214
+ this.face = doc.createElement("button");
3215
+ this.face.type = "button";
3216
+ this.face.className = "face";
3217
+ this.face.setAttribute("aria-haspopup", "true");
3218
+ this.faceIcon = createIconSvg(doc);
3219
+ this.faceLabel = doc.createElement("span");
3220
+ const chevron = createIconSvg(doc);
3221
+ chevron.classList.add("chev");
3222
+ paintIcon(chevron, "chevronDown");
3223
+ this.face.append(this.faceIcon, this.faceLabel, chevron);
3224
+ this.face.addEventListener(
3225
+ "click",
3226
+ () => this.dispatchEvent(
3227
+ new CustomEvent(config3().collapseEvent, { bubbles: true, composed: true })
3228
+ )
3229
+ );
3230
+ group.append(this.face, row, foot);
3091
3231
  root.append(group);
3092
3232
  }
3093
3233
  connectedCallback() {
3094
3234
  this.setAttribute("role", "group");
3095
3235
  this.sync();
3236
+ this.observe();
3237
+ }
3238
+ disconnectedCallback() {
3239
+ this.observer?.disconnect();
3096
3240
  }
3097
3241
  attributeChangedCallback() {
3098
3242
  this.sync();
3099
3243
  }
3244
+ /** The launcher's event detail; null emits nothing. */
3245
+ launcherDetail() {
3246
+ const command = this.getAttribute("launcher");
3247
+ return command ? { command } : null;
3248
+ }
3249
+ /** The registered icon on the collapsed face; a product subclass may map names. */
3250
+ iconName() {
3251
+ return this.getAttribute("icon") ?? "grid";
3252
+ }
3253
+ observe() {
3254
+ this.observer?.disconnect();
3255
+ if (!this.isConnected) return;
3256
+ const Observer = this.ownerDocument.defaultView?.ResizeObserver;
3257
+ if (Observer) this.observer ??= new Observer(() => this.measure());
3258
+ for (const el of this.slotEl.assignedElements()) this.observer?.observe(el);
3259
+ this.measure();
3260
+ }
3261
+ measure() {
3262
+ const children = [...this.children];
3263
+ const stackable = children.filter((el) => {
3264
+ const gallery = el.matches('[mode="dropdown"]') ? el : el.querySelector('[mode="dropdown"]');
3265
+ return gallery !== null && !gallery.hasAttribute("data-command-large");
3266
+ });
3267
+ this.toggleAttribute(
3268
+ "data-stack",
3269
+ stackable.length > 1 && stackable.length === children.length
3270
+ );
3271
+ const tallest = Math.max(0, ...children.map((el) => el.getBoundingClientRect().height));
3272
+ if (tallest > 0)
3273
+ this.toggleAttribute(
3274
+ "data-compact-row",
3275
+ tallest <= tokenPx(this, "--office-ribbon-compact-row-max", 36)
3276
+ );
3277
+ }
3100
3278
  sync() {
3101
3279
  const label = this.getAttribute("label") ?? "";
3102
3280
  this.caption.textContent = label;
3103
3281
  this.setAttribute("aria-label", label);
3104
- this.launcher.hidden = !this.getAttribute("launcher");
3282
+ const launcherLabel = this.getAttribute("launcher-label") ?? `${label} options`;
3283
+ this.launcher.hidden = !this.hasAttribute("launcher");
3105
3284
  this.launcher.disabled = this.hasAttribute("launcher-disabled");
3106
- this.launcher.setAttribute("aria-label", `${label} options`);
3107
- this.launcher.title = `${label} options`;
3285
+ this.launcher.setAttribute("aria-label", launcherLabel);
3286
+ this.launcher.title = launcherLabel;
3287
+ this.faceLabel.textContent = label;
3288
+ this.face.title = label;
3289
+ this.face.setAttribute("aria-label", label);
3290
+ this.face.setAttribute("aria-expanded", String(this.hasAttribute("data-open")));
3291
+ paintIcon(this.faceIcon, this.iconName());
3108
3292
  }
3109
3293
  }
3110
3294
  return OfficeUiRibbonGroup;
@@ -3227,7 +3411,7 @@ function renderSelectMenu(menu, choices, value) {
3227
3411
  });
3228
3412
  menu.replaceChildren(...items);
3229
3413
  }
3230
- function tokenPx(el, name, fallback) {
3414
+ function tokenPx2(el, name, fallback) {
3231
3415
  const value = el.ownerDocument.defaultView?.getComputedStyle(el).getPropertyValue(name).trim();
3232
3416
  const px = value?.endsWith("px") ? parseFloat(value) : NaN;
3233
3417
  return Number.isFinite(px) ? px : fallback;
@@ -3235,14 +3419,14 @@ function tokenPx(el, name, fallback) {
3235
3419
  function positionSelectMenu(menu, trigger, host) {
3236
3420
  const view = menu.ownerDocument.defaultView;
3237
3421
  const rect = trigger.getBoundingClientRect();
3238
- const margin = tokenPx(host, "--office-space-2", 8);
3239
- const gap = tokenPx(host, "--office-space-1", 4);
3422
+ const margin = tokenPx2(host, "--office-space-2", 8);
3423
+ const gap = tokenPx2(host, "--office-space-1", 4);
3240
3424
  const width = Math.max(0, view.innerWidth - margin * 2);
3241
3425
  const below = Math.max(0, view.innerHeight - rect.bottom - gap - margin);
3242
3426
  const above = Math.max(0, rect.top - gap - margin);
3243
3427
  menu.style.minWidth = `${Math.min(rect.width, width)}px`;
3244
3428
  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);
3429
+ const cap = host.getAttribute("data-font-picker") === "family" ? tokenPx2(host, "--office-font-picker-max-height", 320) : tokenPx2(host, "--office-select-max-height", 240);
3246
3430
  menu.style.maxHeight = `${cap}px`;
3247
3431
  const preferred = menu.getBoundingClientRect().height;
3248
3432
  const flip = below < preferred && above > below;
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-EWNLGZPX.js';
2
- export { defineIcon } from './chunk-WNSUNQIY.js';
3
- import './chunk-WKLVWPI7.js';
4
- import './chunk-3BHP4IFP.js';
5
- import './chunk-BDJW2KDI.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-ZBFAPBNU.js';
2
+ export { defineIcon } from './chunk-VUXNXH4Q.js';
3
+ import './chunk-VYF7LGXZ.js';
4
+ import './chunk-NDEB6EBU.js';
5
+ import './chunk-4MT4OIAJ.js';
package/dist/icons.js CHANGED
@@ -1,3 +1,3 @@
1
- export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-WNSUNQIY.js';
2
- import './chunk-3BHP4IFP.js';
3
- import './chunk-BDJW2KDI.js';
1
+ export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-VUXNXH4Q.js';
2
+ import './chunk-NDEB6EBU.js';
3
+ import './chunk-4MT4OIAJ.js';
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
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';
1
+ import { installOfficeUiTheme } from './chunk-F4TM7U4P.js';
2
+ export { THEME_CSS, THEME_STYLE_ID, installOfficeUiTheme } from './chunk-F4TM7U4P.js';
3
+ import { registerControls } from './chunk-ZBFAPBNU.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-ZBFAPBNU.js';
5
+ export { ICON_CSS, createIconSvg, defineIcon, getIcon, listIcons, paintIcon, registerIcon } from './chunk-VUXNXH4Q.js';
6
+ import { definePresence } from './chunk-VYF7LGXZ.js';
7
+ export { definePresence, initialsOf, participantsFromAwareness } from './chunk-VYF7LGXZ.js';
8
+ import { defineSmartArt } from './chunk-A46H2JBV.js';
9
+ export { defineSmartArt, renderDiagramDrawing } from './chunk-A46H2JBV.js';
10
+ export { CONTRACT_REVISION } from './chunk-NDEB6EBU.js';
11
+ export { COMPACT, FOCUS_RING, OFFICE_TOKENS, TOUCH, tok } from './chunk-4MT4OIAJ.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-WKLVWPI7.js';
2
- import './chunk-3BHP4IFP.js';
3
- import './chunk-BDJW2KDI.js';
1
+ export { definePresence, initialsOf, participantsFromAwareness } from './chunk-VYF7LGXZ.js';
2
+ import './chunk-NDEB6EBU.js';
3
+ import './chunk-4MT4OIAJ.js';
package/dist/ribbon.d.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  /**
2
- * Labelled group of ribbon commands: `role="group"`, `label` attribute is caption and name.
3
- * `launcher="<command>"` adds Office's corner dialog launcher, which emits `office-command`
4
- * `{ command }`; `launcher-disabled` disables it.
2
+ * Labelled group of ribbon commands: `role="group"`, `label` is the caption and name. `launcher`
3
+ * adds Office's corner dialog launcher (`launcher="<command>"` emits `office-command`
4
+ * `{ command }`; `launcher-label` names it; `launcher-disabled` disables it). For narrow windows
5
+ * an overflow controller sets `data-collapsed` (the group becomes one button showing `icon` and
6
+ * the label; activating it emits `office-ribbon-collapse-toggle`) and `data-open` (its commands
7
+ * open in a popup at `--office-ribbon-collapse-x/-y`). The group marks itself `data-compact-row`
8
+ * when every command is one line tall (they centre vertically) and `data-stack` when it holds only
9
+ * small drop-down galleries (`[mode="dropdown"]` without `data-command-large`), which stack in
10
+ * columns. Static `launcherEvent`/`collapseEvent` and `launcherDetail()` let a product subclass
11
+ * keep its published events.
5
12
  */
6
13
  export declare const defineRibbonGroup: import("./registry.js").Definer;
7
14
  /**
package/dist/smartart.js CHANGED
@@ -1,3 +1,3 @@
1
- export { defineSmartArt, renderDiagramDrawing } from './chunk-4WUYOQRL.js';
2
- import './chunk-3BHP4IFP.js';
3
- import './chunk-BDJW2KDI.js';
1
+ export { defineSmartArt, renderDiagramDrawing } from './chunk-A46H2JBV.js';
2
+ import './chunk-NDEB6EBU.js';
3
+ import './chunk-4MT4OIAJ.js';
package/dist/theme.js CHANGED
@@ -1,2 +1,2 @@
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';
1
+ export { THEME_CSS, THEME_STYLE_ID, installOfficeUiTheme } from './chunk-F4TM7U4P.js';
2
+ export { COMPACT, FOCUS_RING, OFFICE_TOKENS, TOUCH, tok } from './chunk-4MT4OIAJ.js';
package/dist/tokens.d.ts CHANGED
@@ -124,6 +124,28 @@ export declare const OFFICE_TOKENS: {
124
124
  readonly '--office-ribbon-tab-height': "35px";
125
125
  readonly '--office-ribbon-group-gap': "2px";
126
126
  readonly '--office-ribbon-group-height': "78px";
127
+ readonly '--office-ribbon-group-padding': "4px 8px 2px";
128
+ readonly '--office-ribbon-separator-inset': "0px";
129
+ readonly '--office-ribbon-separator': "color-mix(in srgb, var(--office-border, #d1d5db) 60%, transparent)";
130
+ readonly '--office-ribbon-caption-size': "10px";
131
+ readonly '--office-ribbon-caption-padding': "2px 0 0";
132
+ readonly '--office-launcher-inset-end': "-6px";
133
+ readonly '--office-launcher-inset-bottom': "-1px";
134
+ readonly '--office-ribbon-compact-row-max': "36px";
135
+ readonly '--office-ribbon-stack-max-height': "80px";
136
+ readonly '--office-ribbon-collapse-x': "8px";
137
+ readonly '--office-ribbon-collapse-y': "120px";
138
+ readonly '--office-command-icon-color': "currentColor";
139
+ readonly '--office-command-large-height': "66px";
140
+ readonly '--office-command-large-min-width': "48px";
141
+ readonly '--office-command-large-max-width': "80px";
142
+ readonly '--office-command-large-icon': "32px";
143
+ readonly '--office-command-small-icon': "16px";
144
+ readonly '--office-command-tall-height': "62px";
145
+ readonly '--office-command-tall-min-width': "40px";
146
+ readonly '--office-command-tall-icon': "30px";
147
+ readonly '--office-command-active': "color-mix(in srgb, var(--office-accent, #2563eb) 15%, transparent)";
148
+ readonly '--office-collapsed-group-min-width': "56px";
127
149
  readonly '--office-ribbon-caption-line-height': "12px";
128
150
  readonly '--office-ribbon-row-height': "22px";
129
151
  readonly '--office-ribbon-file-min-width': "52px";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ooxml-ui",
3
- "version": "0.14.0",
3
+ "version": "0.15.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",