@threadlabs/looma 0.22.3 → 0.22.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.22.3",
3
+ "version": "0.22.4",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -91,6 +91,29 @@
91
91
  box-shadow: var(--_ui-tooltip-shadow, var(--ui-overlay-shadow, var(--ui-elevation-tooltip, var(--ui-shadow-md))));
92
92
  }
93
93
 
94
+ /* A keyboard shortcut follows the label after a thin divider, smaller and quieter, as menus show
95
+ it. The label stays first in the description, so a screen reader hears the name, then the keys. */
96
+ .shortcut {
97
+ display: inline-flex;
98
+ align-items: center;
99
+ margin-inline-start: var(--ui-space-2);
100
+ padding-inline-start: var(--ui-space-2);
101
+ border-inline-start: 1px solid color-mix(in oklab, currentColor 28%, transparent);
102
+ color: color-mix(in oklab, currentColor 72%, transparent);
103
+ font-size: var(--ui-font-size-xs);
104
+ font-weight: var(--ui-font-normal);
105
+ white-space: nowrap;
106
+ vertical-align: baseline;
107
+ }
108
+
109
+ .shortcut:empty {
110
+ display: none;
111
+ }
112
+
113
+ .shortcut kbd {
114
+ font: inherit;
115
+ }
116
+
94
117
  :scope[data-ui-tooltip-state~="inverse"] .surface {
95
118
  --_tooltip-line: var(--_ui-tooltip-inverse-surface, var(--ui-text-primary));
96
119
  background: var(--_ui-tooltip-inverse-surface, var(--ui-text-primary));
@@ -193,6 +216,9 @@
193
216
  @scope ([data-component~="ui-tooltip"]) to ([data-component]) {
194
217
  /* This component's hooks style the element they are set on, and never a nested one. */
195
218
 
219
+ /* A keyboard shortcut follows the label after a thin divider, smaller and quieter, as menus show
220
+ it. The label stays first in the description, so a screen reader hears the name, then the keys. */
221
+
196
222
  /* A rotated square shares the surface fill and draws the two outward border edges. Its centre sits on
197
223
  * the surface border line, so the fill covers that stretch of border and the outline runs unbroken. */
198
224
 
@@ -3,7 +3,7 @@ import { manageComponentLifecycle } from "@nextwebwg/html-next/runtime";
3
3
  import * as controller from "../components/ui-tooltip/ui-tooltip.js";
4
4
  import "../styles/ui-tooltip.css";
5
5
 
6
- const definition = {...{"contract":{"tag":"ui-tooltip","props":{"for":{"type":"string","required":false,"target":{"attribute":"for"},"default":""},"hideDelay":{"type":"number","required":false,"target":{"attribute":"hidedelay"},"default":100},"inverse":{"type":"boolean","required":false,"target":{"attribute":"inverse"},"default":false},"open":{"type":"boolean","required":false,"target":{"attribute":"open"},"default":false},"placement":{"type":{"kind":"terminal","name":"keyword"},"values":["top","top-start","top-end","bottom","bottom-start","bottom-end","left","left-start","left-end","right","right-start","right-end"],"required":false,"target":{"attribute":"placement"},"default":"top"},"showDelay":{"type":"number","required":false,"target":{"attribute":"showdelay"},"default":500},"trigger":{"type":{"kind":"terminal","name":"keyword"},"values":["hover","click","focus"],"required":false,"target":{"attribute":"trigger"},"default":"hover"}}},"template":{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"role","value":"tooltip"},{"kind":"attribute","name":"hidden","expression":"not internalOpen","expressionPlan":{"source":"not internalOpen","ast":{"kind":"unary","op":"not","operand":{"kind":"id","name":"internalOpen"}},"dependencies":["internalOpen"]}}],"children":[{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"surface"}],"children":[{"kind":"slot"}]}]},"declarations":[{"kind":"state","name":"internalOpen","type":"boolean","value":"false","expression":{"source":"false","ast":{"kind":"literal","value":false},"dependencies":[]}},{"kind":"event","name":"open","type":"object","shape":{"kind":"object","fields":[{"name":"open","type":{"kind":"terminal","name":"boolean"},"optional":false},{"name":"reason","type":{"kind":"constrained","base":{"kind":"terminal","name":"keyword"},"values":["action","programmatic","light-dismiss","escape"]},"optional":false},{"name":"trigger","type":{"kind":"constrained","base":{"kind":"terminal","name":"keyword"},"values":["keyboard","pointer","programmatic"]},"optional":false}],"open":false},"bubbles":true,"composed":true,"cancelable":false},{"kind":"event","name":"close","type":"object","shape":{"kind":"object","fields":[{"name":"open","type":{"kind":"terminal","name":"boolean"},"optional":false},{"name":"reason","type":{"kind":"constrained","base":{"kind":"terminal","name":"keyword"},"values":["action","programmatic","light-dismiss","escape"]},"optional":false},{"name":"trigger","type":{"kind":"constrained","base":{"kind":"terminal","name":"keyword"},"values":["keyboard","pointer","programmatic"]},"optional":false}],"open":false},"bubbles":true,"composed":true,"cancelable":false}],"root":{"kind":"native","element":"span","choices":["span"]}},source:{file:import.meta.url},css:""};
6
+ const definition = {...{"contract":{"tag":"ui-tooltip","props":{"for":{"type":"string","required":false,"target":{"attribute":"for"},"default":""},"hideDelay":{"type":"number","required":false,"target":{"attribute":"hidedelay"},"default":100},"inverse":{"type":"boolean","required":false,"target":{"attribute":"inverse"},"default":false},"open":{"type":"boolean","required":false,"target":{"attribute":"open"},"default":false},"placement":{"type":{"kind":"terminal","name":"keyword"},"values":["top","top-start","top-end","bottom","bottom-start","bottom-end","left","left-start","left-end","right","right-start","right-end"],"required":false,"target":{"attribute":"placement"},"default":"top"},"showDelay":{"type":"number","required":false,"target":{"attribute":"showdelay"},"default":500},"trigger":{"type":{"kind":"terminal","name":"keyword"},"values":["hover","click","focus"],"required":false,"target":{"attribute":"trigger"},"default":"hover"}}},"template":{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"role","value":"tooltip"},{"kind":"attribute","name":"hidden","expression":"not internalOpen","expressionPlan":{"source":"not internalOpen","ast":{"kind":"unary","op":"not","operand":{"kind":"id","name":"internalOpen"}},"dependencies":["internalOpen"]}}],"children":[{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"surface"}],"children":[{"kind":"slot"},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"shortcut"}],"children":[{"kind":"slot","fallback":[],"name":"shortcut"}]}]}]},"declarations":[{"kind":"state","name":"internalOpen","type":"boolean","value":"false","expression":{"source":"false","ast":{"kind":"literal","value":false},"dependencies":[]}},{"kind":"event","name":"open","type":"object","shape":{"kind":"object","fields":[{"name":"open","type":{"kind":"terminal","name":"boolean"},"optional":false},{"name":"reason","type":{"kind":"constrained","base":{"kind":"terminal","name":"keyword"},"values":["action","programmatic","light-dismiss","escape"]},"optional":false},{"name":"trigger","type":{"kind":"constrained","base":{"kind":"terminal","name":"keyword"},"values":["keyboard","pointer","programmatic"]},"optional":false}],"open":false},"bubbles":true,"composed":true,"cancelable":false},{"kind":"event","name":"close","type":"object","shape":{"kind":"object","fields":[{"name":"open","type":{"kind":"terminal","name":"boolean"},"optional":false},{"name":"reason","type":{"kind":"constrained","base":{"kind":"terminal","name":"keyword"},"values":["action","programmatic","light-dismiss","escape"]},"optional":false},{"name":"trigger","type":{"kind":"constrained","base":{"kind":"terminal","name":"keyword"},"values":["keyboard","pointer","programmatic"]},"optional":false}],"open":false},"bubbles":true,"composed":true,"cancelable":false}],"root":{"kind":"native","element":"span","choices":["span"]}},source:{file:import.meta.url},css:""};
7
7
 
8
8
  export function createUiTooltip(options = {}) {
9
9
  const { attributes = {}, children = [], slots = {}, ...componentProps } = options;
@@ -26,6 +26,18 @@ export function createUiTooltip(options = {}) {
26
26
  }
27
27
  } else {
28
28
  }
29
+ const element1 = document.createElement("span");
30
+ element1.setAttribute("class", "shortcut");
31
+ if (slots["shortcut"] ?? [].length > 0) {
32
+ for (const child of slots["shortcut"] ?? []) {
33
+ const node = typeof child === "string" ? document.createTextNode(child) : child;
34
+ if (node.nodeType === 1) node.setAttribute("data-slotted", "");
35
+ projected.push([node, "shortcut"]);
36
+ element1.append(node);
37
+ }
38
+ } else {
39
+ }
40
+ element0.append(element1);
29
41
  element.append(element0);
30
42
  manageComponentLifecycle(element, definition, { props: componentProps, projected,
31
43
  controller,
@@ -1,7 +1,9 @@
1
1
  import type { PropType } from 'vue';
2
- declare var __VLS_1: {};
2
+ declare var __VLS_1: {}, __VLS_3: {};
3
3
  type __VLS_Slots = {} & {
4
4
  default?: (props: typeof __VLS_1) => any;
5
+ } & {
6
+ shortcut?: (props: typeof __VLS_3) => any;
5
7
  };
6
8
  declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
9
  for: {
package/vue/UiTooltip.vue CHANGED
@@ -312,7 +312,10 @@ useComponentHost(() => import('../components/ui-tooltip/ui-tooltip.js'), {
312
312
  :data-trigger="reflectedProp('trigger', 'trigger', checkedProps.trigger, undefined, false)"
313
313
  :data-ui-tooltip-state="hostState || undefined"
314
314
  ref="root"
315
- ><div class="surface"><slot /></div></span>
315
+ ><div class="surface">
316
+ <slot />
317
+ <span class="shortcut"><slot name="shortcut" /></span>
318
+ </div></span>
316
319
  </template>
317
320
 
318
321
  <style scoped>
@@ -410,6 +413,29 @@ useComponentHost(() => import('../components/ui-tooltip/ui-tooltip.js'), {
410
413
  );
411
414
  }
412
415
 
416
+ /* A keyboard shortcut follows the label after a thin divider, smaller and quieter, as menus show
417
+ it. The label stays first in the description, so a screen reader hears the name, then the keys. */
418
+ .shortcut {
419
+ display: inline-flex;
420
+ align-items: center;
421
+ margin-inline-start: var(--ui-space-2);
422
+ padding-inline-start: var(--ui-space-2);
423
+ border-inline-start: 1px solid color-mix(in oklab, currentColor 28%, transparent);
424
+ color: color-mix(in oklab, currentColor 72%, transparent);
425
+ font-size: var(--ui-font-size-xs);
426
+ font-weight: var(--ui-font-normal);
427
+ white-space: nowrap;
428
+ vertical-align: baseline;
429
+ }
430
+
431
+ .shortcut:empty {
432
+ display: none;
433
+ }
434
+
435
+ .shortcut kbd {
436
+ font: inherit;
437
+ }
438
+
413
439
  [data-component~="ui-tooltip"][data-ui-tooltip-state~="inverse"] .surface {
414
440
  --_tooltip-line: var(--_ui-tooltip-inverse-surface, var(--ui-text-primary));
415
441
  background: var(--_ui-tooltip-inverse-surface, var(--ui-text-primary));
@@ -15,6 +15,7 @@ var _hoisted_1 = [
15
15
  "data-ui-tooltip-state"
16
16
  ];
17
17
  var _hoisted_2 = { class: "surface" };
18
+ var _hoisted_3 = { class: "shortcut" };
18
19
  //#endregion
19
20
  //#region .build/vue/UiTooltip.vue
20
21
  var UiTooltip_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -253,12 +254,12 @@ var UiTooltip_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
253
254
  "data-ui-tooltip-state": hostState.value || void 0,
254
255
  ref_key: "root",
255
256
  ref: root
256
- }), [createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "default", {}, void 0, true)])], 16, _hoisted_1)), [[unref(vPropValidity), {
257
+ }), [createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "default", {}, void 0, true), createElementVNode("span", _hoisted_3, [renderSlot(_ctx.$slots, "shortcut", {}, void 0, true)])])], 16, _hoisted_1)), [[unref(vPropValidity), {
257
258
  contract: propValidityContract,
258
259
  values: { ...checkedProps.value }
259
260
  }]]);
260
261
  };
261
262
  }
262
- }), [["__scopeId", "data-v-0ec1a9c8"]]);
263
+ }), [["__scopeId", "data-v-3762dc6a"]]);
263
264
  //#endregion
264
265
  export { UiTooltip_default as t };
@@ -1484,7 +1484,7 @@ input[data-v-101ef53e]:focus-visible {
1484
1484
  syntax: "*";
1485
1485
  inherits: false;
1486
1486
  }
1487
- [data-component~="ui-tooltip"][data-v-0ec1a9c8] {
1487
+ [data-component~="ui-tooltip"][data-v-3762dc6a] {
1488
1488
  /* Hooks do not inherit, so the parts below read them from the root. */
1489
1489
  --_ui-tooltip-border: var(--ui-tooltip-border);
1490
1490
  --_ui-tooltip-max-inline-size: var(--ui-tooltip-max-inline-size);
@@ -1516,7 +1516,7 @@ input[data-v-101ef53e]:focus-visible {
1516
1516
  display var(--ui-motion-fast) allow-discrete,
1517
1517
  overlay var(--ui-motion-fast) allow-discrete;
1518
1518
  }
1519
- .surface[data-v-0ec1a9c8] {
1519
+ .surface[data-v-3762dc6a] {
1520
1520
  --_tooltip-line: var(--_ui-tooltip-border, var(--ui-overlay-border, var(--ui-border-strong)));
1521
1521
  position: relative;
1522
1522
  box-sizing: border-box;
@@ -1534,7 +1534,28 @@ input[data-v-101ef53e]:focus-visible {
1534
1534
  var(--ui-overlay-shadow, var(--ui-elevation-tooltip, var(--ui-shadow-md)))
1535
1535
  );
1536
1536
  }
1537
- [data-component~="ui-tooltip"][data-ui-tooltip-state~="inverse"] .surface[data-v-0ec1a9c8] {
1537
+
1538
+ /* A keyboard shortcut follows the label after a thin divider, smaller and quieter, as menus show
1539
+ it. The label stays first in the description, so a screen reader hears the name, then the keys. */
1540
+ .shortcut[data-v-3762dc6a] {
1541
+ display: inline-flex;
1542
+ align-items: center;
1543
+ margin-inline-start: var(--ui-space-2);
1544
+ padding-inline-start: var(--ui-space-2);
1545
+ border-inline-start: 1px solid color-mix(in oklab, currentColor 28%, transparent);
1546
+ color: color-mix(in oklab, currentColor 72%, transparent);
1547
+ font-size: var(--ui-font-size-xs);
1548
+ font-weight: var(--ui-font-normal);
1549
+ white-space: nowrap;
1550
+ vertical-align: baseline;
1551
+ }
1552
+ .shortcut[data-v-3762dc6a]:empty {
1553
+ display: none;
1554
+ }
1555
+ .shortcut kbd[data-v-3762dc6a] {
1556
+ font: inherit;
1557
+ }
1558
+ [data-component~="ui-tooltip"][data-ui-tooltip-state~="inverse"] .surface[data-v-3762dc6a] {
1538
1559
  --_tooltip-line: var(--_ui-tooltip-inverse-surface, var(--ui-text-primary));
1539
1560
  background: var(--_ui-tooltip-inverse-surface, var(--ui-text-primary));
1540
1561
  color: var(--_ui-tooltip-inverse-text, var(--ui-surface-default));
@@ -1542,7 +1563,7 @@ input[data-v-101ef53e]:focus-visible {
1542
1563
 
1543
1564
  /* A rotated square shares the surface fill and draws the two outward border edges. Its centre sits on
1544
1565
  * the surface border line, so the fill covers that stretch of border and the outline runs unbroken. */
1545
- .surface[data-v-0ec1a9c8]::after {
1566
+ .surface[data-v-3762dc6a]::after {
1546
1567
  content: "";
1547
1568
  position: absolute;
1548
1569
  z-index: 1;
@@ -1558,7 +1579,7 @@ input[data-v-101ef53e]:focus-visible {
1558
1579
  border-end-end-radius: 1px;
1559
1580
  transform: rotate(45deg) scaleX(1.25) scaleY(0.85);
1560
1581
  }
1561
- [data-component~="ui-tooltip"][data-ui-actual-placement="bottom"] .surface[data-v-0ec1a9c8]::after {
1582
+ [data-component~="ui-tooltip"][data-ui-actual-placement="bottom"] .surface[data-v-3762dc6a]::after {
1562
1583
  inset-block-start: calc(var(--_ui-tooltip-arrow-size, 0.625rem) / -2 - 0.5px);
1563
1584
  inset-block-end: auto;
1564
1585
  border-inline-end-width: 0;
@@ -1568,59 +1589,59 @@ input[data-v-101ef53e]:focus-visible {
1568
1589
  border-end-end-radius: 0;
1569
1590
  border-start-start-radius: 1px;
1570
1591
  }
1571
- [data-component~="ui-tooltip"][data-ui-actual-align="center"] .surface[data-v-0ec1a9c8]::after {
1592
+ [data-component~="ui-tooltip"][data-ui-actual-align="center"] .surface[data-v-3762dc6a]::after {
1572
1593
  inset-inline-start: calc(50% - var(--_ui-tooltip-arrow-size, 0.625rem) / 2);
1573
1594
  }
1574
- [data-component~="ui-tooltip"][data-ui-actual-align="end"] .surface[data-v-0ec1a9c8]::after {
1595
+ [data-component~="ui-tooltip"][data-ui-actual-align="end"] .surface[data-v-3762dc6a]::after {
1575
1596
  inset-inline-start: auto;
1576
1597
  inset-inline-end: var(--ui-space-3);
1577
1598
  }
1578
- [data-component~="ui-tooltip"][data-ui-actual-placement="left"] .surface[data-v-0ec1a9c8]::after,
1579
- [data-component~="ui-tooltip"][data-ui-actual-placement="right"] .surface[data-v-0ec1a9c8]::after {
1599
+ [data-component~="ui-tooltip"][data-ui-actual-placement="left"] .surface[data-v-3762dc6a]::after,
1600
+ [data-component~="ui-tooltip"][data-ui-actual-placement="right"] .surface[data-v-3762dc6a]::after {
1580
1601
  inset-block-start: calc(50% - var(--_ui-tooltip-arrow-size, 0.625rem) / 2);
1581
1602
  inset-block-end: auto;
1582
1603
  border: 0 solid var(--_tooltip-line);
1583
1604
  }
1584
- [data-component~="ui-tooltip"][data-ui-actual-placement="left"] .surface[data-v-0ec1a9c8]::after {
1605
+ [data-component~="ui-tooltip"][data-ui-actual-placement="left"] .surface[data-v-3762dc6a]::after {
1585
1606
  inset-inline-start: auto;
1586
1607
  inset-inline-end: calc(var(--_ui-tooltip-arrow-size, 0.625rem) / -2 - 0.5px);
1587
1608
  border-inline-end-width: 1px;
1588
1609
  border-block-start-width: 1px;
1589
1610
  }
1590
- [data-component~="ui-tooltip"][data-ui-actual-placement="right"] .surface[data-v-0ec1a9c8]::after {
1611
+ [data-component~="ui-tooltip"][data-ui-actual-placement="right"] .surface[data-v-3762dc6a]::after {
1591
1612
  inset-inline-start: calc(var(--_ui-tooltip-arrow-size, 0.625rem) / -2 - 0.5px);
1592
1613
  inset-inline-end: auto;
1593
1614
  border-inline-start-width: 1px;
1594
1615
  border-block-end-width: 1px;
1595
1616
  }
1596
1617
  [data-component~="ui-tooltip"][data-ui-actual-placement="left"][data-ui-actual-align="start"]
1597
- .surface[data-v-0ec1a9c8]::after,
1618
+ .surface[data-v-3762dc6a]::after,
1598
1619
  [data-component~="ui-tooltip"][data-ui-actual-placement="right"][data-ui-actual-align="start"]
1599
- .surface[data-v-0ec1a9c8]::after {
1620
+ .surface[data-v-3762dc6a]::after {
1600
1621
  inset-block-start: var(--ui-space-3);
1601
1622
  }
1602
1623
  [data-component~="ui-tooltip"][data-ui-actual-placement="left"][data-ui-actual-align="end"]
1603
- .surface[data-v-0ec1a9c8]::after,
1624
+ .surface[data-v-3762dc6a]::after,
1604
1625
  [data-component~="ui-tooltip"][data-ui-actual-placement="right"][data-ui-actual-align="end"]
1605
- .surface[data-v-0ec1a9c8]::after {
1626
+ .surface[data-v-3762dc6a]::after {
1606
1627
  inset-block-start: auto;
1607
1628
  inset-block-end: var(--ui-space-3);
1608
1629
  }
1609
- [data-component~="ui-tooltip"][data-v-0ec1a9c8]:popover-open {
1630
+ [data-component~="ui-tooltip"][data-v-3762dc6a]:popover-open {
1610
1631
  opacity: 1;
1611
1632
  transform: translateY(0);
1612
1633
  }
1613
1634
  @starting-style {
1614
- [data-component~="ui-tooltip"][data-v-0ec1a9c8]:popover-open {
1635
+ [data-component~="ui-tooltip"][data-v-3762dc6a]:popover-open {
1615
1636
  opacity: 0;
1616
1637
  transform: translateY(4px);
1617
1638
  }
1618
1639
  }
1619
- [data-component~="ui-tooltip"][hidden][data-v-0ec1a9c8] {
1640
+ [data-component~="ui-tooltip"][hidden][data-v-3762dc6a] {
1620
1641
  display: none;
1621
1642
  }
1622
1643
  @media (prefers-reduced-motion: reduce) {
1623
- [data-component~="ui-tooltip"][data-v-0ec1a9c8] {
1644
+ [data-component~="ui-tooltip"][data-v-3762dc6a] {
1624
1645
  transition: none;
1625
1646
  }
1626
1647
  }
@@ -403,6 +403,23 @@ var autoCodeLanguageNames = [
403
403
  ];
404
404
  //#endregion
405
405
  //#region src/vue/editor/LoomaEditor.ts
406
+ /**
407
+ * Shows a key binding in ProseMirror notation ("Mod-Shift-8") the way the reader's platform writes
408
+ * it: modifier symbols in Apple's order on Apple devices (⇧⌘8), and Ctrl+Shift+8 elsewhere.
409
+ */
410
+ function formatEditorShortcut(keys, apple) {
411
+ const parts = keys.split("-");
412
+ const key = (parts.pop() ?? "").toUpperCase();
413
+ const has = (name) => parts.includes(name);
414
+ if (apple) return `${has("Ctrl") ? "⌃" : ""}${has("Alt") ? "⌥" : ""}${has("Shift") ? "⇧" : ""}${has("Mod") ? "⌘" : ""}${key}`;
415
+ return [
416
+ has("Mod") || has("Ctrl") ? "Ctrl" : "",
417
+ has("Alt") ? "Alt" : "",
418
+ has("Shift") ? "Shift" : "",
419
+ key
420
+ ].filter(Boolean).join("+");
421
+ }
422
+ var isApplePlatform = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.userAgentData?.platform || navigator.platform || navigator.userAgent);
406
423
  var EMPTY_DOCUMENT = {
407
424
  type: "doc",
408
425
  content: []
@@ -606,16 +623,18 @@ var LoomaEditor = defineComponent({
606
623
  const toolbarScope = `looma-editor-tool-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
607
624
  const tooltipFor = ref("");
608
625
  const tooltipLabel = ref("");
626
+ const tooltipShortcut = ref("");
609
627
  const tooltipOpen = ref(false);
610
628
  let tooltipTimer = 0;
611
629
  const clearTooltipTimer = () => {
612
630
  if (tooltipTimer) window.clearTimeout(tooltipTimer);
613
631
  tooltipTimer = 0;
614
632
  };
615
- const showTool = (id, label, immediate) => {
633
+ const showTool = (id, label, immediate, shortcut = "") => {
616
634
  clearTooltipTimer();
617
635
  tooltipFor.value = id;
618
636
  tooltipLabel.value = label;
637
+ tooltipShortcut.value = shortcut ? formatEditorShortcut(shortcut, isApplePlatform()) : "";
619
638
  if (immediate || tooltipOpen.value) {
620
639
  tooltipOpen.value = true;
621
640
  return;
@@ -1507,7 +1526,7 @@ var LoomaEditor = defineComponent({
1507
1526
  const onTableOverlayMouseDown = (event) => {
1508
1527
  if (event.target instanceof Element ? event.target.closest("button[data-action]") : null) event.preventDefault();
1509
1528
  };
1510
- const commandButton = (label, icon, active, disabled, run) => {
1529
+ const commandButton = (label, icon, active, disabled, run, shortcut = "") => {
1511
1530
  const syncNativeDisabled = (vnode) => {
1512
1531
  if (!(vnode.el instanceof Element)) return;
1513
1532
  const button = vnode.el instanceof HTMLButtonElement ? vnode.el : vnode.el.querySelector("button");
@@ -1519,9 +1538,9 @@ var LoomaEditor = defineComponent({
1519
1538
  class: "looma-editor__toolbar-button",
1520
1539
  label,
1521
1540
  size: "sm",
1522
- onPointerenter: () => showTool(id, label, false),
1541
+ onPointerenter: () => showTool(id, label, false, shortcut),
1523
1542
  onPointerleave: hideTool,
1524
- onFocusin: () => showTool(id, label, true),
1543
+ onFocusin: () => showTool(id, label, true, shortcut),
1525
1544
  onFocusout: hideTool,
1526
1545
  variant: "ghost",
1527
1546
  disabled,
@@ -1534,12 +1553,12 @@ var LoomaEditor = defineComponent({
1534
1553
  };
1535
1554
  const renderToolbar = (instance, floating = true, textOnly = false) => {
1536
1555
  const textButtons = [
1537
- commandButton("Bold", "bold", instance.isActive("bold"), !instance.can().toggleBold(), () => instance.chain().focus().toggleBold().run()),
1538
- commandButton("Italic", "italic", instance.isActive("italic"), !instance.can().toggleItalic(), () => instance.chain().focus().toggleItalic().run()),
1539
- commandButton("Underline", "underline", instance.isActive("underline"), !instance.can().toggleUnderline(), () => instance.chain().focus().toggleUnderline().run()),
1540
- commandButton("Strike", "strikethrough", instance.isActive("strike"), !instance.can().toggleStrike(), () => instance.chain().focus().toggleStrike().run()),
1541
- ...authorHighlight ? [commandButton("Highlight", "highlighter", instance.isActive("highlight"), !instance.can().toggleHighlight(), () => instance.chain().focus().toggleHighlight().run())] : [],
1542
- commandButton("Inline code", "code-xml", instance.isActive("code"), !instance.can().toggleCode(), () => instance.chain().focus().toggleCode().run())
1556
+ commandButton("Bold", "bold", instance.isActive("bold"), !instance.can().toggleBold(), () => instance.chain().focus().toggleBold().run(), "Mod-b"),
1557
+ commandButton("Italic", "italic", instance.isActive("italic"), !instance.can().toggleItalic(), () => instance.chain().focus().toggleItalic().run(), "Mod-i"),
1558
+ commandButton("Underline", "underline", instance.isActive("underline"), !instance.can().toggleUnderline(), () => instance.chain().focus().toggleUnderline().run(), "Mod-u"),
1559
+ commandButton("Strike", "strikethrough", instance.isActive("strike"), !instance.can().toggleStrike(), () => instance.chain().focus().toggleStrike().run(), "Mod-Shift-s"),
1560
+ ...authorHighlight ? [commandButton("Highlight", "highlighter", instance.isActive("highlight"), !instance.can().toggleHighlight(), () => instance.chain().focus().toggleHighlight().run(), "Mod-Shift-h")] : [],
1561
+ commandButton("Inline code", "code-xml", instance.isActive("code"), !instance.can().toggleCode(), () => instance.chain().focus().toggleCode().run(), "Mod-e")
1543
1562
  ];
1544
1563
  const buttons = textOnly ? textButtons : [
1545
1564
  ...textButtons,
@@ -1568,13 +1587,13 @@ var LoomaEditor = defineComponent({
1568
1587
  class: "divider",
1569
1588
  "aria-hidden": "true"
1570
1589
  }),
1571
- commandButton("Heading 1", "heading-1", instance.isActive("heading", { level: 1 }), false, () => instance.chain().focus().toggleHeading({ level: 1 }).run()),
1572
- commandButton("Heading 2", "heading-2", instance.isActive("heading", { level: 2 }), false, () => instance.chain().focus().toggleHeading({ level: 2 }).run()),
1573
- commandButton("Heading 3", "heading-3", instance.isActive("heading", { level: 3 }), false, () => instance.chain().focus().toggleHeading({ level: 3 }).run()),
1574
- commandButton("Bullet list", "list", instance.isActive("bulletList"), false, () => instance.chain().focus().toggleBulletList().run()),
1575
- commandButton("Numbered list", "list-ordered", instance.isActive("orderedList"), false, () => instance.chain().focus().toggleOrderedList().run()),
1576
- commandButton("Blockquote", "quote", instance.isActive("blockquote"), !instance.can().toggleBlockquote(), () => instance.chain().focus().toggleBlockquote().run()),
1577
- commandButton("Code block", "braces", instance.isActive("codeBlock"), !instance.can().toggleCodeBlock(), () => instance.chain().focus().toggleCodeBlock().run()),
1590
+ commandButton("Heading 1", "heading-1", instance.isActive("heading", { level: 1 }), false, () => instance.chain().focus().toggleHeading({ level: 1 }).run(), "Mod-Alt-1"),
1591
+ commandButton("Heading 2", "heading-2", instance.isActive("heading", { level: 2 }), false, () => instance.chain().focus().toggleHeading({ level: 2 }).run(), "Mod-Alt-2"),
1592
+ commandButton("Heading 3", "heading-3", instance.isActive("heading", { level: 3 }), false, () => instance.chain().focus().toggleHeading({ level: 3 }).run(), "Mod-Alt-3"),
1593
+ commandButton("Bullet list", "list", instance.isActive("bulletList"), false, () => instance.chain().focus().toggleBulletList().run(), "Mod-Shift-8"),
1594
+ commandButton("Numbered list", "list-ordered", instance.isActive("orderedList"), false, () => instance.chain().focus().toggleOrderedList().run(), "Mod-Shift-7"),
1595
+ commandButton("Blockquote", "quote", instance.isActive("blockquote"), !instance.can().toggleBlockquote(), () => instance.chain().focus().toggleBlockquote().run(), "Mod-Shift-b"),
1596
+ commandButton("Code block", "braces", instance.isActive("codeBlock"), !instance.can().toggleCodeBlock(), () => instance.chain().focus().toggleCodeBlock().run(), "Mod-Alt-c"),
1578
1597
  h(IconButton, {
1579
1598
  id: blockActionAnchorId,
1580
1599
  class: "looma-editor__toolbar-button",
@@ -1607,8 +1626,8 @@ var LoomaEditor = defineComponent({
1607
1626
  class: "divider",
1608
1627
  "aria-hidden": "true"
1609
1628
  }),
1610
- commandButton("Undo", "undo", false, !instance.can().undo(), () => instance.chain().focus().undo().run()),
1611
- commandButton("Redo", "redo", false, !instance.can().redo(), () => instance.chain().focus().redo().run())
1629
+ commandButton("Undo", "undo", false, !instance.can().undo(), () => instance.chain().focus().undo().run(), "Mod-z"),
1630
+ commandButton("Redo", "redo", false, !instance.can().redo(), () => instance.chain().focus().redo().run(), isApplePlatform() ? "Mod-Shift-z" : "Mod-y")
1612
1631
  ];
1613
1632
  return h(EditorToolbar, { floating }, () => [...buttons, h(Tooltip, {
1614
1633
  class: "looma-editor__toolbar-tip",
@@ -1616,7 +1635,10 @@ var LoomaEditor = defineComponent({
1616
1635
  open: tooltipOpen.value,
1617
1636
  placement: floating ? "top" : "bottom",
1618
1637
  trigger: "focus"
1619
- }, () => tooltipLabel.value)]);
1638
+ }, {
1639
+ default: () => tooltipLabel.value,
1640
+ shortcut: () => tooltipShortcut.value ? h("kbd", tooltipShortcut.value) : null
1641
+ })]);
1620
1642
  };
1621
1643
  const focus = (position = "start") => {
1622
1644
  editor.value?.commands.focus(position);