@kungal/ui-core 2.53.0 → 2.55.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/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # @kungal/ui-core
2
2
 
3
+ ## 2.55.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0bac099: New `KunSplitPane`: two panes with a divider you can drag or move with the keyboard. It suits a list beside a detail view, or a main area with an auxiliary column. KunChatLayout now switches between one and two panes by its own width, and its list pane can be made draggable.
8
+
9
+ **KunSplitPane** puts its panes in the `#start` and `#end` slots.
10
+
11
+ - **Width.** `v-model:size` is the primary pane's width in px (default 360). It is a prop the server knows, so the server-rendered HTML already has the layout and the divider's `aria-valuenow`/`min`/`max`.
12
+ - **Persisting the width.** `v-model:size` updates on every pointer move. Save from `resize-end`, which fires once per drag or key press, for example to a cookie your server reads back into `size`.
13
+ - **Limits and snapping.** `min-size` and `max-size` default to 240 and 480. `snap-points` (e.g. `[360, 412]`) pull the divider in once it comes within `snap-threshold` px (default 8), and any width in between stays reachable. A keyboard step that ends within the threshold snaps too, but a snap never cancels or reverses a step, so a small `step` cannot get stuck on a snap point.
14
+ - **Which pane is sized.** `primary="end"` gives the width to the right pane, for an auxiliary column.
15
+ - **Keyboard** (WAI-ARIA window splitter pattern):
16
+ - ← and → move the divider by `step` (default 10px); Shift moves five steps.
17
+ - Home and End jump to the minimum and maximum.
18
+ - **The divider** is a 1px line. Its grab area is 9px wide with a mouse and 21px on a touch screen, and it sits above the panes' content. A drag follows the pointer that started it; a second finger or the mouse cannot take it over. While you drag, the page keeps a col-resize cursor, no text gets selected, and the panes ignore the pointer, so an iframe can't swallow the drag.
19
+ - **Narrow layouts.** Below `stack-below` (`md` = 48rem by default, `lg` = 64rem, or `false`), it shows one pane at a time, chosen by `show-pane`. This is a container query on the component's own width, not a media query on the window: a split nested beside a side rail or inside another pane stacks based on the room it actually has.
20
+ - **Flutter port.** The snapping rule is a pure `resolveKunSplitSize` in `@kungal/ui-core`, with tests.
21
+
22
+ **KunChatLayout** now shows two panes once the layout itself is 48rem (768px) wide, rather than once the window is.
23
+
24
+ - **Why:** a chat beside an app's side rail used to switch to two panes in a 768px window that left it 679px, and the conversation was squeezed to 327px.
25
+ - **What might change for you:** pages that give the chat less than 48rem (for example, a window of about 768–800px minus page padding) now show one pane.
26
+ - **KunChatHeader's `back="mobile"`** follows the KunChatLayout around it. Outside a KunChatLayout it still uses the window's `md` width.
27
+ - **New props:** `resizable`, `v-model:sidebar-size` (px), `sidebar-min-size` and `sidebar-max-size` make the list draggable through KunSplitPane. The `sidebar-resize-end` event is the one to persist from. Without `resizable`, `sidebar-width` works as before.
28
+
29
+ ## 2.54.0
30
+
31
+ ### Minor Changes
32
+
33
+ - 5ca083f: Menus gain separators, shortcut hints and one level of submenu, and a new `KunKbd` shows keyboard shortcuts. Everything is additive: existing `items` arrays and props render exactly as before.
34
+
35
+ **KunContextMenu and KunDropdown** share one item list, so an app can render the same command list as a right-click menu, a ⋯ menu and a long-press menu. `items` now takes `KunMenuEntry[]`, which is an item or `{ type: 'separator' }`. `KunContextMenuItem[]` arrays are still accepted.
36
+
37
+ - **Separators.** Leading, trailing and repeated separators are dropped, so a list filtered per context never shows a stray line.
38
+ - **Shortcuts.** `shortcut: 'Mod+C'` shows ⌘C on Apple and Ctrl+C elsewhere at the item's end. It is display only; your app still binds the key. Screen readers read the item's label as its name and the shortcut as its description.
39
+ - **Submenus.** `children` opens a submenu, one level deep.
40
+ - Opening: hover (after 100 ms), →, Enter or a tap.
41
+ - Closing: ← and Escape close one level, as the WAI-ARIA menu pattern specifies; Tab closes the whole menu.
42
+ - A 300 ms grace period and a safe triangle keep the submenu open while the pointer moves diagonally towards it.
43
+ - An item with `children` never emits `select`; if a filter leaves it no child items, it shows disabled. Selecting a submenu item emits that item and closes everything.
44
+ - **Keyboard.** KunContextMenu now has the type-ahead KunDropdown already had. ↑ in a menu with nothing focused yet, such as right after a click opened it, now goes to the last item. Before, it went to the second-to-last.
45
+ - **Link items.** Enter or Space on an `href` item now follows the link. Before, it emitted `select` and closed without navigating.
46
+
47
+ **KunKbd** takes `keys` in the aria-keyshortcuts grammar plus `Mod`: `+` joins a chord and a space separates a sequence, e.g. `'Mod+K'`, `'Shift+Delete'`, `'G G'`.
48
+
49
+ - **Order.** Modifiers sort into each platform's order: ⌃⌥⇧⌘ on Apple, Ctrl+Win+Alt+Shift elsewhere.
50
+ - **Screen readers** hear key names ("Command K"), not glyphs.
51
+ - **Size** is in em, so it follows the surrounding text.
52
+ - **Variants.** `variant="plain"` is the one-line form menus use: ⇧⌘Z, or Ctrl+Shift+Z.
53
+ - **Server rendering.** The server cannot know the visitor's keyboard, so it renders the Ctrl form; a Mac switches to ⌘ once the page has mounted. Components created afterwards, such as a menu or tooltip opening, start in the right form.
54
+ - **Related APIs:**
55
+ - `KunTooltip` gains `shortcut`: `text="Search" shortcut="Mod+K"` renders "Search Ctrl K".
56
+ - `KunCommandPalette`'s `trigger` slot also passes `keys` for use with KunKbd.
57
+ - `useKunShortcutPlatform()` and `formatKunShortcut()` are exported for your own markup.
58
+ - The parser and formatter live in `@kungal/ui-core`, so the Flutter port renders the same labels.
59
+
60
+ **KunChip and KunTagInput:** the × remove button now has a 24×24 px hit area. The glyph did not change size: it was 14 px in KunChip and 18 px in KunTagInput, which is under WCAG 2.2's 24 px minimum. The hit area is not clipped: in Chromium, a clipped one measured worse than none, because it switches off the browser's own tap adjustment.
61
+
62
+ `useKunFloating`'s `offset` option now accepts floating-ui's full `OffsetOptions` (for example `{ mainAxis, crossAxis }`), not just a number.
63
+
3
64
  ## 2.53.0
4
65
 
5
66
  ## 2.52.1
package/dist/index.cjs CHANGED
@@ -458,6 +458,30 @@ var zh_CN_default = {
458
458
  reveal: "显示密码",
459
459
  hide: "隐藏密码"
460
460
  },
461
+ kbd: {
462
+ control: "Ctrl",
463
+ alt: "Alt",
464
+ option: "Option",
465
+ shift: "Shift",
466
+ command: "Command",
467
+ meta: "Windows 键",
468
+ enter: "回车",
469
+ escape: "Esc",
470
+ backspace: "退格",
471
+ delete: "删除",
472
+ tab: "Tab",
473
+ space: "空格",
474
+ up: "上箭头",
475
+ down: "下箭头",
476
+ left: "左箭头",
477
+ right: "右箭头",
478
+ pageUp: "向上翻页",
479
+ pageDown: "向下翻页",
480
+ home: "Home",
481
+ end: "End",
482
+ plus: "加号",
483
+ then: "然后"
484
+ },
461
485
  lightbox: {
462
486
  label: "图片查看器",
463
487
  close: "关闭",
@@ -517,6 +541,9 @@ var zh_CN_default = {
517
541
  clear: "清除",
518
542
  removeOption: "移除 {label}"
519
543
  },
544
+ splitPane: {
545
+ handle: "调整分栏宽度"
546
+ },
520
547
  spoiler: {
521
548
  copyCode: "复制代码",
522
549
  reveal: "剧透内容,点击或按回车显示"
@@ -552,6 +579,133 @@ var zh_CN_default = {
552
579
  // src/locale/zh-CN.ts
553
580
  var KUN_CATALOG_ZH_CN = zh_CN_default;
554
581
 
582
+ // src/shortcut.ts
583
+ var ALIASES = {
584
+ mod: "Mod",
585
+ ctrl: "Control",
586
+ control: "Control",
587
+ alt: "Alt",
588
+ option: "Alt",
589
+ opt: "Alt",
590
+ shift: "Shift",
591
+ meta: "Meta",
592
+ cmd: "Meta",
593
+ command: "Meta",
594
+ super: "Meta",
595
+ win: "Meta",
596
+ enter: "Enter",
597
+ return: "Enter",
598
+ esc: "Escape",
599
+ escape: "Escape",
600
+ backspace: "Backspace",
601
+ del: "Delete",
602
+ delete: "Delete",
603
+ tab: "Tab",
604
+ space: "Space",
605
+ spacebar: "Space",
606
+ up: "ArrowUp",
607
+ arrowup: "ArrowUp",
608
+ down: "ArrowDown",
609
+ arrowdown: "ArrowDown",
610
+ left: "ArrowLeft",
611
+ arrowleft: "ArrowLeft",
612
+ right: "ArrowRight",
613
+ arrowright: "ArrowRight",
614
+ pageup: "PageUp",
615
+ pgup: "PageUp",
616
+ pagedown: "PageDown",
617
+ pgdn: "PageDown",
618
+ home: "Home",
619
+ end: "End",
620
+ plus: "+"
621
+ };
622
+ var MODIFIER_ORDER = {
623
+ apple: ["Control", "Alt", "Shift", "Meta"],
624
+ other: ["Control", "Meta", "Alt", "Shift"]
625
+ };
626
+ var NAMED = {
627
+ Control: { apple: "⌃", other: "Ctrl", spoken: () => "control" },
628
+ Alt: { apple: "⌥", other: "Alt", spoken: (p) => p === "apple" ? "option" : "alt" },
629
+ Shift: { apple: "⇧", other: "Shift", spoken: () => "shift" },
630
+ Meta: { apple: "⌘", other: "Win", spoken: (p) => p === "apple" ? "command" : "meta" },
631
+ Enter: { apple: "↩", other: "Enter", spoken: () => "enter" },
632
+ Escape: { apple: "Esc", other: "Esc", spoken: () => "escape" },
633
+ Backspace: { apple: "⌫", other: "Backspace", spoken: () => "backspace" },
634
+ Delete: { apple: "⌦", other: "Del", spoken: () => "delete" },
635
+ Tab: { apple: "⇥", other: "Tab", spoken: () => "tab" },
636
+ Space: { apple: "Space", other: "Space", spoken: () => "space" },
637
+ ArrowUp: { apple: "↑", other: "↑", spoken: () => "up" },
638
+ ArrowDown: { apple: "↓", other: "↓", spoken: () => "down" },
639
+ ArrowLeft: { apple: "←", other: "←", spoken: () => "left" },
640
+ ArrowRight: { apple: "→", other: "→", spoken: () => "right" },
641
+ PageUp: { apple: "PgUp", other: "PgUp", spoken: () => "pageUp" },
642
+ PageDown: { apple: "PgDn", other: "PgDn", spoken: () => "pageDown" },
643
+ Home: { apple: "Home", other: "Home", spoken: () => "home" },
644
+ End: { apple: "End", other: "End", spoken: () => "end" },
645
+ "+": { apple: "+", other: "+", spoken: () => "plus" }
646
+ };
647
+ var canonicalKey = (token) => {
648
+ const alias = ALIASES[token.toLowerCase()];
649
+ if (alias) return alias;
650
+ if (/^f\d{1,2}$/i.test(token)) return token.toUpperCase();
651
+ return token.length === 1 ? token.toUpperCase() : token;
652
+ };
653
+ var splitChord = (chord) => {
654
+ if (chord === "+") return ["+"];
655
+ const plusKey = chord.endsWith("++");
656
+ const body = plusKey ? chord.slice(0, -2) : chord;
657
+ const keys = body.split("+").filter(Boolean);
658
+ return plusKey ? [...keys, "+"] : keys;
659
+ };
660
+ var parseKunShortcut = (keys) => keys.trim().split(/\s+/).filter(Boolean).map((chord) => splitChord(chord).map(canonicalKey)).filter((chord) => chord.length > 0);
661
+ var resolveKunShortcut = (keys, platform) => {
662
+ const order = MODIFIER_ORDER[platform];
663
+ return parseKunShortcut(keys).map((chord) => {
664
+ const resolved = chord.map(
665
+ (key) => key === "Mod" ? platform === "apple" ? "Meta" : "Control" : key
666
+ );
667
+ const modifiers = order.filter((m) => resolved.includes(m));
668
+ const rest = resolved.filter((k, i) => !order.includes(k) && resolved.indexOf(k) === i);
669
+ return [...modifiers, ...rest].map((key) => {
670
+ const named = NAMED[key];
671
+ return named ? { key, label: named[platform], spoken: named.spoken(platform) } : { key, label: key, spoken: null };
672
+ });
673
+ });
674
+ };
675
+ var formatKunShortcut = (keys, platform) => resolveKunShortcut(keys, platform).map((chord) => chord.map((k) => k.label).join(platform === "apple" ? "" : "+")).join(" ");
676
+ var speakKunShortcut = (sequence, name, then) => sequence.map((chord) => chord.map((k) => k.spoken ? name(k.spoken) : k.label).join(" ")).join(` ${then} `);
677
+
678
+ // src/menu.ts
679
+ var normalizeKunMenuSeparators = (entries) => {
680
+ const out = [];
681
+ for (const entry of entries) {
682
+ const last = out[out.length - 1];
683
+ if (entry.type === "separator" && (!last || last.type === "separator")) continue;
684
+ out.push(entry);
685
+ }
686
+ while (out.length && out[out.length - 1].type === "separator") out.pop();
687
+ return out;
688
+ };
689
+
690
+ // src/splitPane.ts
691
+ var resolveKunSplitSize = (raw, { min, max, snapPoints = [], snapThreshold = 8, from }) => {
692
+ const lo = Math.min(min, max);
693
+ const hi = Math.max(min, max);
694
+ const clamped = Math.min(Math.max(raw, lo), hi);
695
+ let best = clamped;
696
+ let bestDistance = Infinity;
697
+ for (const point of snapPoints) {
698
+ if (point < lo || point > hi) continue;
699
+ const distance = Math.abs(point - clamped);
700
+ if (distance <= snapThreshold && distance < bestDistance) {
701
+ best = point;
702
+ bestDistance = distance;
703
+ }
704
+ }
705
+ if (from !== void 0 && Math.sign(best - from) !== Math.sign(clamped - from)) best = clamped;
706
+ return Math.round(best);
707
+ };
708
+
555
709
  // src/random.ts
556
710
  var randomNum = (lowerValue, upperValue) => Math.floor(Math.random() * (upperValue - lowerValue + 1) + lowerValue);
557
711
 
@@ -1582,6 +1736,7 @@ exports.buildKunChatEntityTree = buildKunChatEntityTree;
1582
1736
  exports.cn = cn;
1583
1737
  exports.decodeIfEncoded = decodeIfEncoded;
1584
1738
  exports.formatKunChatMarkdown = formatKunChatMarkdown;
1739
+ exports.formatKunShortcut = formatKunShortcut;
1585
1740
  exports.getKunIcon = getKunIcon;
1586
1741
  exports.getRandomSticker = getRandomSticker;
1587
1742
  exports.groupKunChatMessages = groupKunChatMessages;
@@ -1608,13 +1763,18 @@ exports.kunTextClasses = kunTextClasses;
1608
1763
  exports.kunVariantClasses = kunVariantClasses;
1609
1764
  exports.layoutKunChatAlbum = layoutKunChatAlbum;
1610
1765
  exports.normalizeKunChatEntities = normalizeKunChatEntities;
1766
+ exports.normalizeKunMenuSeparators = normalizeKunMenuSeparators;
1611
1767
  exports.parseKunChatMarkdown = parseKunChatMarkdown;
1768
+ exports.parseKunShortcut = parseKunShortcut;
1612
1769
  exports.pickAvatarFallback = pickAvatarFallback;
1613
1770
  exports.randomNum = randomNum;
1614
1771
  exports.registerKunIcon = registerKunIcon;
1615
1772
  exports.registerKunIcons = registerKunIcons;
1773
+ exports.resolveKunShortcut = resolveKunShortcut;
1774
+ exports.resolveKunSplitSize = resolveKunSplitSize;
1616
1775
  exports.resolveRounded = resolveRounded;
1617
1776
  exports.sliceKunChatEntities = sliceKunChatEntities;
1777
+ exports.speakKunShortcut = speakKunShortcut;
1618
1778
  exports.translateKunMessage = translateKunMessage;
1619
1779
  //# sourceMappingURL=index.cjs.map
1620
1780
  //# sourceMappingURL=index.cjs.map