@keyfunc/keybricks-ds 2.4.13 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/DEPLOY.md +4 -4
  2. package/INTEGRATION.md +28 -6
  3. package/_adherence.oxlintrc.json +10 -4
  4. package/_ds_bundle.js +20559 -0
  5. package/_src/icon-index.js +1 -0
  6. package/_src/unicons-index.js +1 -0
  7. package/_src/usage-code.js +737 -0
  8. package/changelog.js +17 -0
  9. package/components/_cards/accordion.card.html +1 -1
  10. package/components/_cards/action-chip.card.html +1 -1
  11. package/components/_cards/activity.card.html +1 -1
  12. package/components/_cards/alert.card.html +1 -1
  13. package/components/_cards/app-chrome.card.html +1 -1
  14. package/components/_cards/banner.card.html +1 -1
  15. package/components/_cards/breadcrumb.card.html +1 -1
  16. package/components/_cards/button-1-base.card.html +1 -1
  17. package/components/_cards/button-2-states.card.html +1 -1
  18. package/components/_cards/button-3-sizes-icons.card.html +1 -1
  19. package/components/_cards/button-4-group.card.html +1 -1
  20. package/components/_cards/cards-indicators.card.html +1 -1
  21. package/components/_cards/charts-1-sparkline.card.html +1 -1
  22. package/components/_cards/charts-10-gauge.card.html +1 -1
  23. package/components/_cards/charts-11-funnel.card.html +1 -1
  24. package/components/_cards/charts-12-treemap.card.html +1 -1
  25. package/components/_cards/charts-14-radar.card.html +1 -1
  26. package/components/_cards/charts-15-distribution.card.html +1 -1
  27. package/components/_cards/charts-16-pareto.card.html +1 -1
  28. package/components/_cards/charts-17-candlestick.card.html +1 -1
  29. package/components/_cards/charts-18-sankey.card.html +1 -1
  30. package/components/_cards/charts-19-chord.card.html +1 -1
  31. package/components/_cards/charts-2-line.card.html +1 -1
  32. package/components/_cards/charts-20-sunburst.card.html +1 -1
  33. package/components/_cards/charts-21-worldmap.card.html +1 -1
  34. package/components/_cards/charts-22-gantt.card.html +1 -1
  35. package/components/_cards/charts-23-bar3d.card.html +1 -1
  36. package/components/_cards/charts-24-pie3d.card.html +1 -1
  37. package/components/_cards/charts-25-surface3d.card.html +1 -1
  38. package/components/_cards/charts-26-scatter3d.card.html +1 -1
  39. package/components/_cards/charts-27-ribbon3d.card.html +1 -1
  40. package/components/_cards/charts-3-bar.card.html +1 -1
  41. package/components/_cards/charts-4-barlist.card.html +1 -1
  42. package/components/_cards/charts-5-donut.card.html +1 -1
  43. package/components/_cards/charts-6-scatter.card.html +1 -1
  44. package/components/_cards/charts-7-heatmap.card.html +1 -1
  45. package/components/_cards/charts-8-waterfall.card.html +1 -1
  46. package/components/_cards/charts-9-bullet.card.html +1 -1
  47. package/components/_cards/checkbox.card.html +1 -1
  48. package/components/_cards/codeblock.card.html +1 -1
  49. package/components/_cards/combobox.card.html +1 -1
  50. package/components/_cards/context-menu.card.html +1 -1
  51. package/components/_cards/date-picker.card.html +1 -1
  52. package/components/_cards/diffvalue.card.html +1 -1
  53. package/components/_cards/divider.card.html +1 -1
  54. package/components/_cards/drawer.card.html +1 -1
  55. package/components/_cards/dropdown-menu.card.html +1 -1
  56. package/components/_cards/dropzone.card.html +1 -1
  57. package/components/_cards/empty-state.card.html +1 -1
  58. package/components/_cards/icon-button.card.html +1 -1
  59. package/components/_cards/input.card.html +1 -1
  60. package/components/_cards/kbd.card.html +1 -1
  61. package/components/_cards/keyvalue.card.html +1 -1
  62. package/components/_cards/listrow.card.html +1 -1
  63. package/components/_cards/loading.card.html +1 -1
  64. package/components/_cards/log-viewer.card.html +1 -1
  65. package/components/_cards/multi-select.card.html +1 -1
  66. package/components/_cards/number-stepper.card.html +1 -1
  67. package/components/_cards/pagination.card.html +1 -1
  68. package/components/_cards/people.card.html +1 -1
  69. package/components/_cards/pipeline-node.card.html +1 -1
  70. package/components/_cards/pipeline-palette.card.html +1 -1
  71. package/components/_cards/radio.card.html +1 -1
  72. package/components/_cards/relative-time-picker.card.html +1 -1
  73. package/components/_cards/search.card.html +1 -1
  74. package/components/_cards/segmented-control.card.html +1 -1
  75. package/components/_cards/select.card.html +1 -1
  76. package/components/_cards/smart-table.card.html +1 -1
  77. package/components/_cards/spreadsheet.card.html +1 -1
  78. package/components/_cards/stepper.card.html +1 -1
  79. package/components/_cards/table.card.html +7 -2
  80. package/components/_cards/tabs.card.html +1 -1
  81. package/components/_cards/tag.card.html +1 -1
  82. package/components/_cards/textarea.card.html +1 -1
  83. package/components/_cards/timeline.card.html +1 -1
  84. package/components/_cards/toast.card.html +1 -1
  85. package/components/_cards/toggles.card.html +1 -1
  86. package/components/_cards/tooltip.card.html +1 -1
  87. package/components/_cards/tree.card.html +1 -1
  88. package/components/_shared/view-source.js +15 -185
  89. package/components/charts/BarList.jsx +1 -1
  90. package/components/charts/BarList.vue +8 -3
  91. package/components/charts/Ribbon3D.d.ts +2 -0
  92. package/components/charts/Scatter3D.d.ts +4 -0
  93. package/components/charts/SunburstChart.jsx +6 -1
  94. package/components/charts/SunburstChart.vue +7 -1
  95. package/components/charts/Surface3D.d.ts +2 -0
  96. package/components/charts/WorldMap.jsx +6 -1
  97. package/components/charts/WorldMap.vue +7 -1
  98. package/components/data/ListRow.d.ts +6 -3
  99. package/components/data/ListRow.jsx +21 -13
  100. package/components/data/ListRow.vue +25 -15
  101. package/components/data/Table.jsx +1 -1
  102. package/components/data/Table.vue +6 -2
  103. package/components/display/ProgressBar.jsx +1 -1
  104. package/components/display/ProgressBar.vue +1 -1
  105. package/components/feedback/Accordion.jsx +1 -1
  106. package/components/feedback/Accordion.vue +1 -1
  107. package/components/feedback/ContextMenu.jsx +3 -3
  108. package/components/feedback/ContextMenu.vue +4 -4
  109. package/components/feedback/Drawer.jsx +3 -3
  110. package/components/feedback/Drawer.vue +3 -3
  111. package/components/feedback/Timeline.vue +4 -1
  112. package/components/forms/Checkbox.d.ts +2 -0
  113. package/components/forms/Checkbox.jsx +9 -4
  114. package/components/forms/Checkbox.vue +6 -2
  115. package/components/forms/DatePicker.jsx +3 -1
  116. package/components/forms/DatePicker.vue +2 -1
  117. package/components/forms/Dropzone.jsx +1 -1
  118. package/components/forms/Dropzone.vue +1 -1
  119. package/components/forms/MultiSelect.jsx +5 -3
  120. package/components/forms/MultiSelect.vue +4 -3
  121. package/components/forms/Slider.jsx +1 -1
  122. package/components/forms/Slider.vue +1 -1
  123. package/components/forms/Switch.jsx +4 -4
  124. package/components/forms/Switch.vue +4 -4
  125. package/components/navigation/Sidebar.jsx +2 -2
  126. package/components/navigation/Sidebar.vue +2 -2
  127. package/components/navigation/TopBar.jsx +1 -1
  128. package/components/navigation/TopBar.vue +1 -1
  129. package/components/navigation/Tree.jsx +2 -2
  130. package/components/navigation/Tree.vue +14 -5
  131. package/components/pipeline/DataPreview.jsx +1 -1
  132. package/components/pipeline/DataPreview.vue +1 -1
  133. package/components/pipeline/NodeCard.jsx +1 -1
  134. package/components/pipeline/NodeCard.vue +1 -1
  135. package/components/pipeline/PaletteItem.vue +1 -1
  136. package/components/pipeline/PalettePanel.jsx +3 -3
  137. package/components/pipeline/PalettePanel.vue +2 -2
  138. package/components/tabs/SegmentedControl.jsx +2 -2
  139. package/components/tabs/SegmentedControl.vue +2 -2
  140. package/index.html +6 -56
  141. package/package.json +4 -2
  142. package/tokens/dark.css +1 -1
  143. package/tokens/interaction.css +4 -0
  144. package/tokens/layout.css +3 -0
  145. package/tokens/typography.css +2 -1
@@ -62,18 +62,18 @@ export function Switch({ checked = false, onChange, disabled, size, tone = "prim
62
62
  const glyphText = pick(knobText, checked);
63
63
  const knobX = checked ? `calc(100% - ${H} + 2px)` : 2;
64
64
 
65
- const knob = <span aria-hidden="true" style={{ position: "absolute", top: 2, left: knobX, width: KNOB, height: KNOB, borderRadius: "var(--radius-full)", background: "var(--bg-surface)", boxShadow: "0 1px 2px rgba(0,0,0,.18)", transition: "left 160ms ease-out", display: "flex", alignItems: "center", justifyContent: "center", color: track, fontSize: TEXT, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", overflow: "hidden" }}>
65
+ const knob = <span aria-hidden="true" style={{ position: "absolute", top: 2, left: knobX, width: KNOB, height: KNOB, borderRadius: "var(--radius-full)", background: "var(--bg-surface)", boxShadow: "var(--shadow-thumb)", transition: "left var(--dur) var(--ease-out)", display: "flex", alignItems: "center", justifyContent: "center", color: track, fontSize: TEXT, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", overflow: "hidden" }}>
66
66
  {loading
67
67
  ? <span style={{ width: GLYPH, height: GLYPH, borderRadius: "50%", border: `1.5px solid ${track}`, borderRightColor: "transparent", animation: "kb-spin 720ms linear infinite" }}></span>
68
68
  : glyphIcon ? <Icon name={glyphIcon} size={10} strokeWidth={2.5} style={{ width: GLYPH, height: GLYPH }} />
69
69
  : glyphText ? <span>{String(glyphText).slice(0, 1)}</span> : null}
70
70
  </span>;
71
71
 
72
- const insideStyle = (side) => ({ position: "absolute", top: 0, bottom: 0, [side]: 6, display: "flex", alignItems: "center", fontSize: TEXT, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", letterSpacing: ".2px", color: "var(--on-accent)", transition: "opacity 160ms ease-out", pointerEvents: "none", whiteSpace: "nowrap" });
72
+ const insideStyle = (side) => ({ position: "absolute", top: 0, bottom: 0, [side]: 6, display: "flex", alignItems: "center", fontSize: TEXT, lineHeight: 1, fontWeight: "var(--weight-semibold)", fontFamily: "var(--font-sans)", letterSpacing: ".2px", color: "var(--on-accent)", transition: "opacity var(--dur) var(--ease-out)", pointerEvents: "none", whiteSpace: "nowrap" });
73
73
 
74
74
  const pill = <span role="switch" aria-checked={checked} aria-busy={loading || undefined} aria-disabled={disabled || undefined} tabIndex={inert ? -1 : 0} onClick={flip}
75
75
  onKeyDown={e => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); flip(); } }}
76
- style={{ position: "relative", display: "inline-block", width: w, height: H, borderRadius: "var(--radius-full)", "--sw-track": track, background: "var(--sw-bg, var(--sw-track))", transition: "background 160ms ease-out", cursor: inert ? (loading ? "progress" : "not-allowed") : "pointer", flexShrink: 0, verticalAlign: "middle", outline: "none" }}>
76
+ style={{ position: "relative", display: "inline-block", width: w, height: H, borderRadius: "var(--radius-full)", "--sw-track": track, background: "var(--sw-bg, var(--sw-track))", transition: "background var(--dur) var(--ease-out)", cursor: inert ? (loading ? "progress" : "not-allowed") : "pointer", flexShrink: 0, verticalAlign: "middle", outline: "none" }}>
77
77
  {hasInside && <span style={{ ...insideStyle("left"), opacity: checked ? 1 : 0 }}>{insideOn}</span>}
78
78
  {hasInside && <span style={{ ...insideStyle("right"), opacity: checked ? 0 : 1 }}>{insideOff}</span>}
79
79
  {knob}
@@ -83,7 +83,7 @@ export function Switch({ checked = false, onChange, disabled, size, tone = "prim
83
83
  if (single) return <span data-size={size} data-sw="" style={{ display: "inline-flex", alignItems: "center", verticalAlign: "middle", gap: "var(--control-gap)", opacity: disabled ? .5 : 1, ...style }}>{pill}<span onClick={flip} style={{ ...capBase, fontWeight: "var(--weight-regular)", color: "var(--text-body)" }}>{label}</span></span>;
84
84
  if (labelOn == null && labelOff == null) return <span data-size={size} style={{ display: "inline-flex", alignItems: "center", verticalAlign: "middle", opacity: disabled ? .5 : 1, ...style }}>{pill}</span>;
85
85
 
86
- const cap = (text, active) => text == null ? null : <span style={{ ...capBase, fontWeight: active ? "var(--weight-medium)" : "var(--weight-regular)", color: active ? "var(--text-heading)" : "var(--text-muted)", transition: "color 160ms ease-out" }} onClick={flip}>{text}</span>;
86
+ const cap = (text, active) => text == null ? null : <span style={{ ...capBase, fontWeight: active ? "var(--weight-medium)" : "var(--weight-regular)", color: active ? "var(--text-heading)" : "var(--text-muted)", transition: "color var(--dur) var(--ease-out)" }} onClick={flip}>{text}</span>;
87
87
  return <span data-size={size} data-sw="" style={{ display: "inline-flex", alignItems: "center", verticalAlign: "middle", gap: "var(--control-gap)", opacity: disabled ? .5 : 1, ...style }}>
88
88
  {cap(labelOff, !checked)}{pill}{cap(labelOn, checked)}
89
89
  </span>;
@@ -44,17 +44,17 @@ async function flip() {
44
44
  }
45
45
  emit('update:checked', next); emit('change', next)
46
46
  }
47
- const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]: '6px', display: 'flex', alignItems: 'center', fontSize: TEXT, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', letterSpacing: '.2px', color: 'var(--on-accent)', transition: 'opacity 160ms ease-out', pointerEvents: 'none', whiteSpace: 'nowrap', opacity: on === props.checked ? 1 : 0 })
48
- const cap = (active) => ({ fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', fontFamily: 'var(--font-sans)', fontWeight: active ? 'var(--weight-medium)' : 'var(--weight-regular)', color: active ? 'var(--text-heading)' : 'var(--text-muted)', transition: 'color 160ms ease-out', cursor: inert.value ? 'default' : 'pointer', userSelect: 'none' })
47
+ const insideStyle = (side, on) => ({ position: 'absolute', top: 0, bottom: 0, [side]: '6px', display: 'flex', alignItems: 'center', fontSize: TEXT, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', letterSpacing: '.2px', color: 'var(--on-accent)', transition: 'opacity var(--dur) var(--ease-out)', pointerEvents: 'none', whiteSpace: 'nowrap', opacity: on === props.checked ? 1 : 0 })
48
+ const cap = (active) => ({ fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', fontFamily: 'var(--font-sans)', fontWeight: active ? 'var(--weight-medium)' : 'var(--weight-regular)', color: active ? 'var(--text-heading)' : 'var(--text-muted)', transition: 'color var(--dur) var(--ease-out)', cursor: inert.value ? 'default' : 'pointer', userSelect: 'none' })
49
49
  </script>
50
50
  <template>
51
51
  <span :data-size="size" data-sw="" :style="{ display: 'inline-flex', alignItems: 'center', verticalAlign: 'middle', gap: 'var(--control-gap)', opacity: disabled ? .5 : 1, ...style }">
52
52
  <span v-if="labelOff != null" :style="cap(!checked)" @click="flip">{{ labelOff }}</span>
53
53
  <span role="switch" :aria-checked="checked" :aria-busy="isLoading || undefined" :aria-disabled="disabled || undefined" :tabindex="inert ? -1 : 0" @click="flip" @keydown.space.prevent="flip" @keydown.enter.prevent="flip"
54
- :style="{ position: 'relative', display: 'inline-block', width: width, height: H, borderRadius: 'var(--radius-full)', '--sw-track': track, background: 'var(--sw-bg, var(--sw-track))', transition: 'background 160ms ease-out', cursor: inert ? (isLoading ? 'progress' : 'not-allowed') : 'pointer', flexShrink: 0, verticalAlign: 'middle', outline: 'none' }">
54
+ :style="{ position: 'relative', display: 'inline-block', width: width, height: H, borderRadius: 'var(--radius-full)', '--sw-track': track, background: 'var(--sw-bg, var(--sw-track))', transition: 'background var(--dur) var(--ease-out)', cursor: inert ? (isLoading ? 'progress' : 'not-allowed') : 'pointer', flexShrink: 0, verticalAlign: 'middle', outline: 'none' }">
55
55
  <span v-if="hasInside" :style="insideStyle('left', true)">{{ inOn }}</span>
56
56
  <span v-if="hasInside" :style="insideStyle('right', false)">{{ inOff }}</span>
57
- <span aria-hidden="true" :style="{ position: 'absolute', top: '2px', left: knobX, width: KNOB, height: KNOB, borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', boxShadow: '0 1px 2px rgba(0,0,0,.18)', transition: 'left 160ms ease-out', display: 'flex', alignItems: 'center', justifyContent: 'center', color: track, fontSize: TEXT, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', overflow: 'hidden' }">
57
+ <span aria-hidden="true" :style="{ position: 'absolute', top: '2px', left: knobX, width: KNOB, height: KNOB, borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', boxShadow: 'var(--shadow-thumb)', transition: 'left var(--dur) var(--ease-out)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: track, fontSize: TEXT, lineHeight: 1, fontWeight: 'var(--weight-semibold)', fontFamily: 'var(--font-sans)', overflow: 'hidden' }">
58
58
  <span v-if="isLoading" :style="{ width: GLYPH, height: GLYPH, borderRadius: '50%', border: '1.5px solid ' + track, borderRightColor: 'transparent', animation: 'kb-spin 720ms linear infinite' }"></span>
59
59
  <Icon v-else-if="glyphIcon" :name="glyphIcon" :size="10" :stroke-width="2.5" :style="{ width: GLYPH, height: GLYPH }" />
60
60
  <span v-else-if="glyphText">{{ glyphText }}</span>
@@ -36,10 +36,10 @@ export function Sidebar({ items = KEYBRICKS_NAV, footerItems = KEYBRICKS_NAV_FOO
36
36
  </div>
37
37
  ) : <NavItem key={it.label} icon={it.icon} label={it.label} collapsed={collapsed} trailing={it.trailing} active={active === it.label} onClick={() => pick(it.label)} />);
38
38
  return (
39
- <aside data-theme={t.theme} style={{ ...t.vars, width: collapsed ? "var(--rail-w)" : "var(--sidebar-w)", height, background: "var(--sb-bg)", borderRight: "1px solid var(--sb-border)", color: "var(--sb-text)", display: "flex", flexDirection: "column", boxSizing: "border-box", flexShrink: 0, transition: "width .15s ease", ...style }}>
39
+ <aside data-theme={t.theme} style={{ ...t.vars, width: collapsed ? "var(--rail-w)" : "var(--sidebar-w)", height, background: "var(--sb-bg)", borderRight: "1px solid var(--sb-border)", color: "var(--sb-text)", display: "flex", flexDirection: "column", boxSizing: "border-box", flexShrink: 0, transition: "width var(--dur-fast) var(--ease-out)", ...style }}>
40
40
  <div style={{ height: "var(--topbar-h)", display: "flex", alignItems: "center", gap: 10, padding: "0 16px", boxSizing: "border-box", justifyContent: collapsed ? "center" : "flex-start", flexShrink: 0 }}>
41
41
  <img src={logo} alt="" style={{ width: 24, height: 24, objectFit: "contain", flexShrink: 0 }} />
42
- {!collapsed ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-md)", lineHeight: "var(--leading-md)", letterSpacing: "var(--tracking-md)", fontWeight: "var(--weight-semibold)", color: "var(--sb-brand)", whiteSpace: "nowrap" }}>{brand}</span> : null}
42
+ {!collapsed ? <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-md)", lineHeight: "var(--leading-md)", letterSpacing: "var(--tracking-md)", fontWeight: "var(--weight-semibold)", color: "var(--sb-brand)", whiteSpace: "nowrap", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>{brand}</span> : null}
43
43
  </div>
44
44
  <nav style={{ flex: 1, overflowY: "auto", overflowX: "hidden", padding: 8, display: "flex", flexDirection: "column", gap: 2 }}>
45
45
  {onToggleCollapse ? <NavItem icon={collapsed ? "panel-left" : "chevron-left"} label={collapsed ? L.expand : L.collapse} collapsed={collapsed} onClick={onToggleCollapse} /> : null}
@@ -21,10 +21,10 @@ const groups = computed(() => [{ id: 'main', list: props.items }, { id: 'foot',
21
21
  const versionText = computed(() => props.collapsed && props.version ? 'v' + String(props.version).split(' ').pop().replace(/^v/i, '') : props.version)
22
22
  </script>
23
23
  <template>
24
- <aside :data-theme="t.theme" :style="{ ...t.vars, width: collapsed ? 'var(--rail-w)' : 'var(--sidebar-w)', height, background: 'var(--sb-bg)', borderRight: '1px solid var(--sb-border)', color: 'var(--sb-text)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', flexShrink: 0, transition: 'width .15s ease', ...style }">
24
+ <aside :data-theme="t.theme" :style="{ ...t.vars, width: collapsed ? 'var(--rail-w)' : 'var(--sidebar-w)', height, background: 'var(--sb-bg)', borderRight: '1px solid var(--sb-border)', color: 'var(--sb-text)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', flexShrink: 0, transition: 'width var(--dur-fast) var(--ease-out)', ...style }">
25
25
  <div :style="{ height: 'var(--topbar-h)', display: 'flex', alignItems: 'center', gap: '10px', padding: '0 16px', boxSizing: 'border-box', justifyContent: collapsed ? 'center' : 'flex-start', flexShrink: 0 }">
26
26
  <img :src="logo" alt="" style="width:24px;height:24px;object-fit:contain;flex-shrink:0" />
27
- <span v-if="!collapsed" style="font-family:var(--font-sans);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);font-weight:var(--weight-semibold);color:var(--sb-brand);white-space:nowrap">{{ brand }}</span>
27
+ <span v-if="!collapsed" style="font-family:var(--font-sans);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);font-weight:var(--weight-semibold);color:var(--sb-brand);white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis">{{ brand }}</span>
28
28
  </div>
29
29
  <template v-for="g in groups" :key="g.id">
30
30
  <component :is="g.id === 'main' ? 'nav' : 'div'" :style="g.id === 'main' ? { flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: '8px', display: 'flex', flexDirection: 'column', gap: '2px' } : { padding: '8px', display: 'flex', flexDirection: 'column', gap: '2px', flexShrink: 0 }">
@@ -56,7 +56,7 @@ export function TopBar({ placeholder = "Search documents, workflows, data...", o
56
56
  <IconButton icon="circle-help" label={helpLabel} title={helpLabel} onClick={onHelp} />
57
57
  {user ? <div role="button" tabIndex={0} aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen(!open)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen(!open); } }} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
58
58
  style={{ height: 32, display: "flex", alignItems: "center", gap: 8, padding: "0 8px 0 10px", borderRadius: "var(--radius-md)", cursor: "pointer", userSelect: "none", background: open ? "var(--bg-active)" : hover ? "var(--bg-hover)" : "transparent", transition: "var(--t-hover)" }}>
59
- <span style={{ ...TXT, fontWeight: "var(--weight-medium)", color: "var(--text-heading)", whiteSpace: "nowrap" }}>{user.name}</span>
59
+ <span style={{ ...TXT, fontWeight: "var(--weight-medium)", color: "var(--text-heading)", whiteSpace: "nowrap", minWidth: 0, maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis" }}>{user.name}</span>
60
60
  <span style={{ display: "inline-flex", color: "var(--text-muted)" }}><Icon name="chevron-down" size={16} /></span>
61
61
  </div> : null}
62
62
  </>)}
@@ -27,7 +27,7 @@ function key(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault();
27
27
  <IconButton icon="circle-help" :label="helpLabel" :title="helpLabel" @click="emit('help')" />
28
28
  <div v-if="user" role="button" tabindex="0" aria-haspopup="menu" :aria-expanded="open" @click="open = !open" @keydown="key" @mouseenter="hover = true" @mouseleave="hover = false"
29
29
  :style="{ height: '32px', display: 'flex', alignItems: 'center', gap: '8px', padding: '0 8px 0 10px', borderRadius: 'var(--radius-md)', cursor: 'pointer', userSelect: 'none', background: open ? 'var(--bg-active)' : hover ? 'var(--bg-hover)' : 'transparent', transition: 'var(--t-hover)' }">
30
- <span style="font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);font-weight:var(--weight-medium);color:var(--text-heading);white-space:nowrap">{{ user.name }}</span>
30
+ <span style="font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);font-weight:var(--weight-medium);color:var(--text-heading);white-space:nowrap;min-width:0;max-width:160px;overflow:hidden;text-overflow:ellipsis">{{ user.name }}</span>
31
31
  <span style="display:inline-flex;color:var(--text-muted)"><Icon name="chevron-down" :size="16" /></span>
32
32
  </div>
33
33
  </slot>
@@ -166,7 +166,7 @@ function Row({ item, state, cfg, api }) {
166
166
  {chevron}
167
167
  {cfg.checkable && node.checkable !== false ? (
168
168
  <span onClick={(e) => e.stopPropagation()} style={{ display: "inline-flex", flexShrink: 0 }}>
169
- <Checkbox checked={checked} indeterminate={half} disabled={disabled || node.disableCheckbox} onChange={() => api.check(key)} />
169
+ <Checkbox checked={checked} indeterminate={half} disabled={disabled || node.disableCheckbox} tabIndex={-1} onChange={() => api.check(key)} />
170
170
  </span>
171
171
  ) : null}
172
172
  {cfg.showIcon ? (
@@ -353,7 +353,7 @@ export function Tree({
353
353
  const cfg = { checkable, checkStrictly, multiple, selectable, showIcon, showLine, draggable, searchValue, onContextMenu, rowActions };
354
354
 
355
355
  return (
356
- <div role="tree" aria-multiselectable={multiple || undefined} style={{ display: "flex", flexDirection: "column", gap: showLine ? 0 : 1, ...style }}>
356
+ <div role="tree" aria-multiselectable={multiple || undefined} style={{ display: "flex", flexDirection: "column", gap: showLine ? 0 : 1, minWidth: 0, ...style }}>
357
357
  <style>{"@keyframes kb-tree-spin{to{transform:rotate(360deg)}}"}</style>
358
358
  {rows.length === 0 ? (
359
359
  <div style={{ padding: "10px 8px", fontFamily: "var(--font-sans)", fontSize: "var(--text-base)", color: "var(--text-muted)" }}>No matching items</div>
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { ref, computed, watch } from 'vue'
2
+ import { ref, computed, watch, getCurrentInstance } from 'vue'
3
3
  import Icon from '../icons/Icon.vue'
4
4
  import Checkbox from '../forms/Checkbox.vue'
5
5
  // Nested tree view: expand/collapse, single or multiple selection, cascading checkboxes with
@@ -33,7 +33,12 @@ const props = defineProps({
33
33
  reorder: Boolean,
34
34
  loadData: Function, rowActions: Function, style: Object,
35
35
  })
36
- const emit = defineEmits(['select', 'check', 'expand', 'drop'])
36
+ // @context-menu(key, event) — like React's onContextMenu; only a listener suppresses the browser menu.
37
+ const emit = defineEmits(['select', 'check', 'expand', 'drop', 'contextMenu'])
38
+ const treeInst = getCurrentInstance()
39
+ function onCtx(e, key) { if (treeInst && treeInst.vnode.props && treeInst.vnode.props.onContextMenu) { e.preventDefault(); emit('contextMenu', key, e) } }
40
+ const Cell = (p) => p.v
41
+ Cell.props = ['v']
37
42
 
38
43
  const data = ref(JSON.parse(JSON.stringify(props.treeData || props.items)))
39
44
  // Compare by value: re-seeding on identity alone would wipe async-loaded children.
@@ -219,7 +224,7 @@ function parts(text) {
219
224
  const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
220
225
  </script>
221
226
  <template>
222
- <div role="tree" :style="{ display: 'flex', flexDirection: 'column', gap: showLine ? 0 : '1px', ...style }">
227
+ <div role="tree" :style="{ display: 'flex', flexDirection: 'column', gap: showLine ? 0 : '1px', minWidth: 0, ...style }">
223
228
  <div v-for="row in rows" :key="row.key" role="treeitem" class="kb-tree-row"
224
229
  :tabindex="focus === row.key || (!focus && row === rows[0]) ? 0 : -1"
225
230
  :aria-expanded="isBranch(row.n) ? openKeys.includes(row.key) : undefined"
@@ -227,7 +232,7 @@ const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
227
232
  :draggable="draggable && !row.n.disabled"
228
233
  @dragstart="dragKey = row.key" @dragover="onDragOver($event, row.key)" @dragleave="drop = null"
229
234
  @drop.prevent="onDrop(row.key)" @dragend="dragKey = null; drop = null"
230
- @click="rowClick(row.n)" @keydown="keyDown($event, row)"
235
+ @click="rowClick(row.n)" @contextmenu="onCtx($event, row.key)" @keydown="keyDown($event, row)"
231
236
  :class="{ 'is-disabled': row.n.disabled }"
232
237
  :style="{
233
238
  position: 'relative', minHeight: 'var(--h-tree-item)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: '6px',
@@ -253,7 +258,7 @@ const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
253
258
  <span v-else :style="{ width: showLine ? 0 : '14px', flexShrink: 0 }" />
254
259
  <span v-if="checkable && row.n.checkable !== false" @click.stop style="display:inline-flex;flex-shrink:0">
255
260
  <Checkbox :checked="view.full.has(row.key)" :indeterminate="view.half.has(row.key)"
256
- :disabled="row.n.disabled || row.n.disableCheckbox" @change="check(row.key)" />
261
+ :disabled="row.n.disabled || row.n.disableCheckbox" :tabindex="-1" @change="check(row.key)" />
257
262
  </span>
258
263
  <span v-if="showIcon" :style="{ color: isBranch(row.n) ? 'var(--text-muted)' : docHues[row.n.hue || 'gray'], display: 'inline-flex', flexShrink: 0 }">
259
264
  <Icon :name="row.n.icon || (isBranch(row.n) ? (openKeys.includes(row.key) ? 'folder-open' : 'folder') : 'file-text')" :size="15" />
@@ -263,6 +268,8 @@ const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
263
268
  </span>
264
269
  <span v-if="row.n.tag" style="font-family:var(--font-sans);font-size:var(--text-xs);line-height:1;padding:2px 5px;border-radius:var(--radius-sm);background:var(--bg-inset);color:var(--text-secondary);flex-shrink:0">{{ row.n.tag }}</span>
265
270
  <span v-if="row.n.count != null" style="margin-left:auto;font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-muted);padding-left:8px">{{ row.n.count }}</span>
271
+ <!-- row actions: #row-actions="{ node }" slot, or rowActions(node) returning a VNode; shown on hover / focus -->
272
+ <span v-if="(rowActions || $slots['row-actions']) && !row.n.disabled" class="kb-tree-actions" @click.stop :style="{ marginLeft: row.n.count != null ? '6px' : 'auto', display: 'flex', gap: '2px', flexShrink: 0 }"><slot name="row-actions" :node="row.n"><Cell v-if="rowActions" :v="rowActions(row.n)" /></slot></span>
266
273
  </div>
267
274
  <div v-if="!rows.length" style="padding:10px 8px;font-family:var(--font-sans);font-size:var(--text-base);color:var(--text-muted)">No matching items</div>
268
275
  </div>
@@ -270,6 +277,8 @@ const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
270
277
  <style scoped>
271
278
  .kb-tree-row:not(.is-disabled):hover{background:var(--bg-hover)}
272
279
  .kb-tree-row:focus-visible{outline:2px solid var(--focus-border);outline-offset:-2px}
280
+ .kb-tree-actions{opacity:0;transition:var(--t-hover)}
281
+ .kb-tree-row:hover .kb-tree-actions,.kb-tree-row:focus-within .kb-tree-actions{opacity:1}
273
282
  .kb-tree-spin{animation:kb-tree-spin 900ms linear infinite}
274
283
  @keyframes kb-tree-spin{to{transform:rotate(360deg)}}
275
284
  </style>
@@ -7,7 +7,7 @@ import { Table } from "../data/Table.jsx";
7
7
  /** Bottom data-preview drawer — 250px: 40px header (title, Debug Mode, collapse) + dense table. An sm size region: the header's controls are 24px and the table has 32px rows with 12px text. */
8
8
  export function DataPreview({ title = "Data Preview", columns = [], rows = [], debug = false, onDebug, collapsed, onCollapse, size = "sm", style }) {
9
9
  return (
10
- <div data-size={size} style={{ background: "var(--bg-surface)", borderTop: "1px solid var(--border-card)", display: "flex", flexDirection: "column", height: collapsed ? "var(--bar-card-h)" : "var(--drawer-h)", overflow: "hidden", boxSizing: "border-box", flexShrink: 0, transition: "height .15s ease", ...style }}>
10
+ <div data-size={size} style={{ background: "var(--bg-surface)", borderTop: "1px solid var(--border-card)", display: "flex", flexDirection: "column", height: collapsed ? "var(--bar-card-h)" : "var(--drawer-h)", overflow: "hidden", boxSizing: "border-box", flexShrink: 0, transition: "height var(--dur-fast) var(--ease-out)", ...style }}>
11
11
  <div style={{ height: "var(--bar-card-h)", display: "flex", alignItems: "center", gap: "var(--group-gap)", padding: "0 var(--edge-pad)", boxSizing: "border-box", flexShrink: 0 }}>
12
12
  <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-semibold)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", color: "var(--text-heading)" }}>{title}</span>
13
13
  <span style={{ flex: 1 }} />
@@ -7,7 +7,7 @@ defineProps({ title: { type: String, default: 'Data Preview' }, columns: { type:
7
7
  const emit = defineEmits(['update:debug', 'toggleCollapse'])
8
8
  </script>
9
9
  <template>
10
- <div :data-size="size" :style="{ background: 'var(--bg-surface)', borderTop: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', height: collapsed ? 'var(--bar-card-h)' : 'var(--drawer-h)', overflow: 'hidden', boxSizing: 'border-box', flexShrink: 0, transition: 'height .15s ease', ...style }">
10
+ <div :data-size="size" :style="{ background: 'var(--bg-surface)', borderTop: '1px solid var(--border-card)', display: 'flex', flexDirection: 'column', height: collapsed ? 'var(--bar-card-h)' : 'var(--drawer-h)', overflow: 'hidden', boxSizing: 'border-box', flexShrink: 0, transition: 'height var(--dur-fast) var(--ease-out)', ...style }">
11
11
  <div style="height:var(--bar-card-h);display:flex;align-items:center;gap:var(--group-gap);padding:0 var(--edge-pad);box-sizing:border-box;flex-shrink:0">
12
12
  <span style="font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)">{{ title }}</span>
13
13
  <span style="flex:1" />
@@ -51,7 +51,7 @@ export function PortLabel({ label = "A", tone = "default", interactive = false,
51
51
  export function NodeCard({ title, subtitle, icon = "table", subtitleIcon, hue = "source", selected, badge, inputs = [], output = true, showMenu = true, onClick, onMenu, onOutputDown, onInputUp, onInputEnter, onInputLeave, outputTone = "default", inputTones = [], style }) {
52
52
  const live = !!(onOutputDown || onInputUp);
53
53
  return (
54
- <div onClick={onClick} style={{ position: "relative", width: 220, height: 90, borderRadius: "var(--radius-node)", background: "var(--bg-surface)", border: `1px solid ${selected ? "var(--selected-border)" : "var(--border-control)"}`, boxShadow: selected ? "var(--shadow-selected)" : "var(--shadow-node)", boxSizing: "border-box", cursor: "pointer", ...style }}>
54
+ <div onClick={onClick} role={onClick ? "button" : undefined} tabIndex={onClick ? 0 : undefined} onKeyDown={onClick ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(e); } } : undefined} style={{ position: "relative", width: 220, height: 90, borderRadius: "var(--radius-node)", background: "var(--bg-surface)", border: `1px solid ${selected ? "var(--selected-border)" : "var(--border-control)"}`, boxShadow: selected ? "var(--shadow-selected)" : "var(--shadow-node)", boxSizing: "border-box", cursor: "pointer", ...style }}>
55
55
  <div style={{ display: "flex", alignItems: "center", gap: 12, padding: 16, height: "100%", boxSizing: "border-box" }}>
56
56
  <span style={{ position: "relative", flexShrink: 0 }}>
57
57
  <IconTile icon={icon} hue={hue} kind="node" />
@@ -29,7 +29,7 @@ const pillBase = { width: '28px', height: '22px', borderRadius: 'var(--radius-fu
29
29
  const tone = i => props.inputTones[i] || 'default'
30
30
  </script>
31
31
  <template>
32
- <div @click="emit('click')" :style="{ position: 'relative', width: '220px', height: '90px', borderRadius: 'var(--radius-node)', background: 'var(--bg-surface)', border: `1px solid ${selected ? 'var(--selected-border)' : 'var(--border-control)'}`, boxShadow: selected ? 'var(--shadow-selected)' : 'var(--shadow-node)', boxSizing: 'border-box', cursor: 'pointer', ...style }">
32
+ <div role="button" tabindex="0" @keydown.enter.prevent="emit('click')" @keydown.space.prevent="emit('click')" @click="emit('click')" :style="{ position: 'relative', width: '220px', height: '90px', borderRadius: 'var(--radius-node)', background: 'var(--bg-surface)', border: `1px solid ${selected ? 'var(--selected-border)' : 'var(--border-control)'}`, boxShadow: selected ? 'var(--shadow-selected)' : 'var(--shadow-node)', boxSizing: 'border-box', cursor: 'pointer', ...style }">
33
33
  <div style="display:flex;align-items:center;gap:12px;padding:16px;height:100%;box-sizing:border-box">
34
34
  <span style="position:relative;flex-shrink:0">
35
35
  <IconTile :icon="icon" :hue="hue" kind="node" />
@@ -8,7 +8,7 @@ const hover = ref(false)
8
8
  </script>
9
9
  <template>
10
10
  <div role="button" tabindex="0" @click="$emit('click')" @mouseenter="hover = true" @mouseleave="hover = false"
11
- :style="{ width: '67.664px', borderRadius: 'var(--radius-lg)', padding: '8px', display: 'flex', flexDirection: 'column', gap: '6px', alignItems: 'center', cursor: 'grab', boxSizing: 'border-box', background: hover ? 'var(--bg-hover)' : 'transparent', transition: 'background .12s ease', ...style }">
11
+ :style="{ width: '67.664px', borderRadius: 'var(--radius-lg)', padding: '8px', display: 'flex', flexDirection: 'column', gap: '6px', alignItems: 'center', cursor: 'grab', boxSizing: 'border-box', background: hover ? 'var(--bg-hover)' : 'transparent', transition: 'background var(--dur-fast) var(--ease-out)', ...style }">
12
12
  <IconTile :icon="icon" :hue="hue" kind="palette" />
13
13
  <span style="font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);letter-spacing:var(--tracking-xs);color:var(--text-body);text-align:center">{{ label }}</span>
14
14
  </div>
@@ -9,7 +9,7 @@ export function PaletteItem({ icon, hue, label, onClick, style }) {
9
9
  const [hover, setHover] = React.useState(false);
10
10
  return (
11
11
  <div role="button" tabIndex={0} onClick={onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
12
- style={{ width: 67.664, borderRadius: "var(--radius-lg)", padding: 8, display: "flex", flexDirection: "column", gap: 6, alignItems: "center", cursor: "grab", boxSizing: "border-box", background: hover ? "var(--bg-hover)" : "transparent", transition: "background .12s ease", ...style }}>
12
+ style={{ width: 67.664, borderRadius: "var(--radius-lg)", padding: 8, display: "flex", flexDirection: "column", gap: 6, alignItems: "center", cursor: "grab", boxSizing: "border-box", background: hover ? "var(--bg-hover)" : "transparent", transition: "background var(--dur-fast) var(--ease-out)", ...style }}>
13
13
  <IconTile icon={icon} hue={hue} kind="palette" />
14
14
  <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-xs)", letterSpacing: "var(--tracking-xs)", color: "var(--text-body)", textAlign: "center" }}>{label}</span>
15
15
  </div>
@@ -44,8 +44,8 @@ export function PalettePanel({ catalog = PIPELINE_CATALOG, height = "100%", onPi
44
44
  <div style={{ flex: 1, overflowY: "auto", padding: 8 }}>
45
45
  {catalog.map((cat) => (
46
46
  <div key={cat.label}>
47
- <div onClick={() => toggle(cat.label)} data-wash="" style={{ height: "var(--h-subnav-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", boxSizing: "border-box", cursor: "pointer", background: "var(--wash, transparent)", transition: "var(--t-hover)" }}>
48
- <span style={{ color: "var(--text-muted)", display: "inline-flex", transform: closed.includes(cat.label) ? "rotate(-90deg)" : "none", transition: "transform .15s ease" }}><Icon name="chevron-down" size={14} /></span>
47
+ <div role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggle(cat.label); } }} onClick={() => toggle(cat.label)} data-wash="" style={{ height: "var(--h-subnav-item)", borderRadius: "var(--radius-md)", display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", boxSizing: "border-box", cursor: "pointer", background: "var(--wash, transparent)", transition: "var(--t-hover)" }}>
48
+ <span style={{ color: "var(--text-muted)", display: "inline-flex", transform: closed.includes(cat.label) ? "rotate(-90deg)" : "none", transition: "transform var(--dur-fast) var(--ease-out)" }}><Icon name="chevron-down" size={14} /></span>
49
49
  <span style={{ color: `var(--cat-${cat.hue})`, display: "inline-flex" }}><Icon name={cat.icon} size={14} /></span>
50
50
  <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--text-base)", lineHeight: "var(--leading-base)", letterSpacing: "var(--tracking-base)", color: "var(--text-strong)" }}>{cat.label}</span>
51
51
  </div>
@@ -21,8 +21,8 @@ function toggle(l) { closed.value = closed.value.includes(l) ? closed.value.filt
21
21
  </div>
22
22
  <div style="flex:1;overflow-y:auto;padding:8px">
23
23
  <div v-for="cat in catalog" :key="cat.label">
24
- <div @click="toggle(cat.label)" data-wash="" style="height:var(--h-subnav-item);border-radius:var(--radius-md);display:flex;align-items:center;gap:8px;padding:6px 8px;box-sizing:border-box;cursor:pointer;background:var(--wash, transparent);transition:var(--t-hover)">
25
- <span :style="{ color: 'var(--text-muted)', display: 'inline-flex', transform: closed.includes(cat.label) ? 'rotate(-90deg)' : 'none', transition: 'transform .15s ease' }"><Icon name="chevron-down" :size="14" /></span>
24
+ <div role="button" tabindex="0" @keydown.enter.prevent="toggle(cat.label)" @keydown.space.prevent="toggle(cat.label)" @click="toggle(cat.label)" data-wash="" style="height:var(--h-subnav-item);border-radius:var(--radius-md);display:flex;align-items:center;gap:8px;padding:6px 8px;box-sizing:border-box;cursor:pointer;background:var(--wash, transparent);transition:var(--t-hover)">
25
+ <span :style="{ color: 'var(--text-muted)', display: 'inline-flex', transform: closed.includes(cat.label) ? 'rotate(-90deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }"><Icon name="chevron-down" :size="14" /></span>
26
26
  <span :style="{ color: `var(--cat-${cat.hue})`, display: 'inline-flex' }"><Icon :name="cat.icon" :size="14" /></span>
27
27
  <span style="font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-strong)">{{ cat.label }}</span>
28
28
  </div>
@@ -28,8 +28,8 @@ export function SegmentedControl({ options = [], value, onChange, size, iconOnly
28
28
  return (
29
29
  <button key={o.label} type="button" role="tab" aria-selected={is} aria-label={iconOnly ? o.label : undefined} title={iconOnly ? o.label : undefined} disabled={off} tabIndex={is ? 0 : -1} data-ink={is || off ? undefined : ""}
30
30
  onClick={() => !off && onChange && onChange(o.label)}
31
- style={{ height: "calc(var(--control-h) - 6px)", padding: iconOnly ? "0 calc(var(--control-px) - 4px)" : "0 calc(var(--control-px) - 2px)", flex: fullWidth ? 1 : "none", justifyContent: "center", display: "inline-flex", alignItems: "center", gap: 6, borderRadius: "var(--radius-sm)", border: "none", cursor: off ? "not-allowed" : "pointer", background: is ? "var(--bg-surface)" : "transparent", boxShadow: is ? "0 1px 2px rgba(0,0,0,.08)" : "none", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: is ? "var(--text-heading)" : "var(--ink, var(--text-secondary))", opacity: o.disabled && !disabled ? .45 : 1, whiteSpace: "nowrap", transition: "var(--t-hover)" }}>
32
- {o.icon ? <Icon name={o.icon} size={16} style={tierIcon} /> : null}{iconOnly ? null : o.label}{iconOnly ? null : <CountBadge count={o.count} selected={is} showZero={o.showZero ?? showZero} />}
31
+ style={{ height: "calc(var(--control-h) - 6px)", padding: iconOnly ? "0 calc(var(--control-px) - 4px)" : "0 calc(var(--control-px) - 2px)", flex: fullWidth ? 1 : "none", justifyContent: "center", display: "inline-flex", alignItems: "center", gap: 6, borderRadius: "var(--radius-sm)", border: "none", cursor: off ? "not-allowed" : "pointer", background: is ? "var(--bg-surface)" : "transparent", boxShadow: is ? "var(--shadow-raised)" : "none", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: is ? "var(--text-heading)" : "var(--ink, var(--text-secondary))", opacity: o.disabled && !disabled ? .45 : 1, whiteSpace: "nowrap", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", transition: "var(--t-hover)" }}>
32
+ {o.icon ? <Icon name={o.icon} size={16} style={tierIcon} /> : null}{iconOnly ? null : <span style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>{o.label}</span>}{iconOnly ? null : <CountBadge count={o.count} selected={is} showZero={o.showZero ?? showZero} />}
33
33
  </button>
34
34
  );
35
35
  })}
@@ -21,8 +21,8 @@ function onKey(e) {
21
21
  <template>
22
22
  <div role="tablist" :data-size="size" :aria-disabled="disabled || undefined" @keydown="onKey" :style="{ display: fullWidth ? 'flex' : 'inline-flex', height: 'var(--control-h)', padding: '2px', gap: '2px', background: 'var(--bg-inset)', border: '1px solid var(--border-control)', borderRadius: 'var(--radius-md)', boxSizing: 'border-box', opacity: disabled ? .5 : 1, ...style }">
23
23
  <button v-for="o in opts" :key="o.label" type="button" role="tab" :aria-selected="modelValue === o.label" :aria-label="iconOnly ? o.label : undefined" :title="iconOnly ? o.label : undefined" :disabled="disabled || o.disabled" :tabindex="modelValue === o.label ? 0 : -1" :data-ink="modelValue === o.label || disabled || o.disabled ? undefined : ''" @click="pick(o)"
24
- :style="{ height: 'calc(var(--control-h) - 6px)', padding: iconOnly ? '0 calc(var(--control-px) - 4px)' : '0 calc(var(--control-px) - 2px)', flex: fullWidth ? 1 : 'none', justifyContent: 'center', display: 'inline-flex', alignItems: 'center', gap: '6px', borderRadius: 'var(--radius-sm)', border: 'none', cursor: (disabled || o.disabled) ? 'not-allowed' : 'pointer', background: modelValue === o.label ? 'var(--bg-surface)' : 'transparent', boxShadow: modelValue === o.label ? '0 1px 2px rgba(0,0,0,.08)' : 'none', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: modelValue === o.label ? 'var(--text-heading)' : 'var(--ink, var(--text-secondary))', opacity: o.disabled && !disabled ? .45 : 1, whiteSpace: 'nowrap', transition: 'var(--t-hover)' }">
25
- <Icon v-if="o.icon" :name="o.icon" :size="16" :style="tierIcon" /><template v-if="!iconOnly">{{ o.label }}<CountBadge :count="o.count" :selected="modelValue === o.label" :show-zero="o.showZero ?? showZero" /></template>
24
+ :style="{ height: 'calc(var(--control-h) - 6px)', padding: iconOnly ? '0 calc(var(--control-px) - 4px)' : '0 calc(var(--control-px) - 2px)', flex: fullWidth ? 1 : 'none', justifyContent: 'center', display: 'inline-flex', alignItems: 'center', gap: '6px', borderRadius: 'var(--radius-sm)', border: 'none', cursor: (disabled || o.disabled) ? 'not-allowed' : 'pointer', background: modelValue === o.label ? 'var(--bg-surface)' : 'transparent', boxShadow: modelValue === o.label ? 'var(--shadow-raised)' : 'none', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: modelValue === o.label ? 'var(--text-heading)' : 'var(--ink, var(--text-secondary))', opacity: o.disabled && !disabled ? .45 : 1, whiteSpace: 'nowrap', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', transition: 'var(--t-hover)' }">
25
+ <Icon v-if="o.icon" :name="o.icon" :size="16" :style="tierIcon" /><template v-if="!iconOnly"><span style="min-width:0;overflow:hidden;text-overflow:ellipsis">{{ o.label }}</span><CountBadge :count="o.count" :selected="modelValue === o.label" :show-zero="o.showZero ?? showZero" /></template>
26
26
  </button>
27
27
  </div>
28
28
  </template>
package/index.html CHANGED
@@ -59,22 +59,6 @@ section{margin-bottom:52px;scroll-margin-top:78px}
59
59
  .frame-wrap{position:relative;border:1px solid var(--border-card);border-radius:var(--radius-lg);background:var(--bg-surface);overflow:hidden}
60
60
  .frame-wrap iframe{border:0;display:block;transform-origin:top left;background:var(--bg-surface)}
61
61
  .frame-ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--font-sans);font-variant-numeric:tabular-nums;font-size:var(--text-xs);color:var(--text-muted);background:var(--bg-inset);letter-spacing:var(--tracking-xs)}
62
- .code-area{margin-top:14px}
63
- .code-btn{display:inline-flex;align-items:center;gap:6px;height:28px;box-sizing:border-box;padding:0 8px;line-height:var(--leading-xs);border-radius:var(--radius-md);border:1px solid var(--border-control);background:var(--bg-surface);color:var(--text-secondary);font-size:var(--text-xs);font-family:var(--font-sans);cursor:pointer;transition:var(--t-hover);letter-spacing:var(--tracking-xs)}
64
- .code-btn:hover{background:var(--bg-hover);color:var(--text-heading)}
65
- .code-panel{display:none;margin-top:10px;border:1px solid var(--border-card);border-radius:var(--radius-lg);overflow:hidden;background:var(--bg-inset)}
66
- .code-panel.open{display:block}
67
- .code-head{display:flex;align-items:center;justify-content:space-between;background:var(--bg-surface);border-bottom:1px solid var(--border-subtle)}
68
- .code-tabs{display:flex;flex:1;overflow-x:auto;scrollbar-width:none}
69
- .code-tabs::-webkit-scrollbar{display:none}
70
- .code-tab{padding:8px 12px;border:none;background:transparent;font-size:var(--text-xs);font-family:var(--font-sans);color:var(--text-secondary);cursor:pointer;white-space:nowrap;border-bottom:2px solid transparent;margin-bottom:-1px;letter-spacing:var(--tracking-xs)}
71
- .code-tab.on{color:var(--text-heading);border-bottom-color:var(--action-primary)}
72
- .code-fw{display:flex;gap:3px;padding:6px 8px;flex-shrink:0}
73
- .code-fw{display:flex;align-items:center;flex-shrink:0;height:28px;box-sizing:border-box;gap:2px;padding:2px;margin:0 8px;border-radius:var(--radius-md);background:var(--bg-inset);border:1px solid var(--border-control)}.fw-btn{display:inline-flex;align-items:center;height:22px;padding:0 6px;font-size:var(--text-xs);line-height:var(--leading-xs);border-radius:var(--radius-sm);border:none;background:transparent;color:var(--text-secondary);font-family:var(--font-sans);font-weight:var(--weight-medium);white-space:nowrap;cursor:pointer;transition:var(--t-hover);letter-spacing:var(--tracking-xs)}.fw-btn:not(.on):hover{color:var(--text-heading)}
74
- .fw-btn.on{background:var(--bg-surface);color:var(--text-heading);box-shadow:0 1px 2px rgba(0,0,0,.08)}
75
- .code-body{position:relative}
76
- .code-body pre{margin:0;padding:14px 70px 14px 16px;overflow:auto;max-height:420px;font-family:var(--font-mono);font-size:var(--text-code);line-height:var(--leading-code);color:var(--text-body);white-space:pre}
77
- .copy-btn{position:absolute;top:8px;right:10px;display:inline-flex;align-items:center;height:28px;box-sizing:border-box;padding:0 8px;border-radius:var(--radius-md);border:1px solid var(--border-control);background:var(--bg-surface);color:var(--text-secondary);font-size:var(--text-xs);line-height:var(--leading-xs);font-family:var(--font-sans);cursor:pointer;letter-spacing:var(--tracking-xs)}
78
62
 
79
63
  /* overview */
80
64
  #overview{margin-bottom:64px}
@@ -192,7 +176,7 @@ section{margin-bottom:52px;scroll-margin-top:78px}
192
176
  const GROUPS=[["Brand","品牌","ブランド"],["Colors","颜色","カラー"],["Type","字体排印","タイポグラフィ"],["Foundations","基础规范","基盤仕様"],["Components","组件","コンポーネント"],["Charts","图表","チャート"],["Icons","图标库","アイコン"]];
193
177
 
194
178
  const S={
195
- brandsub:["Design System · v2.4.13","设计系统 · v2.4.13","デザインシステム · v2.4.13"],
179
+ brandsub:["Design System · v2.5.1","设计系统 · v2.5.1","デザインシステム · v2.5.1"],
196
180
  doc_readme:["Readme","说明文档","ドキュメント"],
197
181
  doc_design:["DESIGN.md","下载 DESIGN.md","DESIGN.md"],
198
182
  doc_integration:["Integration","接入指南","導入ガイド"],
@@ -258,6 +242,7 @@ function setLang(l){
258
242
  document.getElementById("filter").placeholder=pick(S.filter_ph);
259
243
  document.querySelectorAll('a[data-readme]').forEach(a=>{a.href=(l==="zh"||l==="both")?"readme.zh-CN.md":"readme.md";});
260
244
  if(typeof fitChangelogTags==="function")fitChangelogTags();
245
+ syncFrames();
261
246
  }
262
247
 
263
248
  /* ---------- overview ---------- */
@@ -451,6 +436,8 @@ function renderNav(){
451
436
  let followSystem=true;
452
437
  const themeOf=()=>html.getAttribute("data-theme")||"light";
453
438
  function pushTheme(f){
439
+ /* Cards also need the site language: the "Show code" bars under each example follow it. */
440
+ try{f.contentWindow&&f.contentWindow.postMessage({kb:"lang",lang},"*");}catch(e){}
454
441
  if(f.dataset.alwaysdark==="1"||f.dataset.selfmanaged==="1")return;
455
442
  const t=themeOf();
456
443
  try{f.contentWindow&&f.contentWindow.postMessage({kb:"theme",theme:t},"*");}catch(e){}
@@ -473,49 +460,12 @@ if(mq&&mq.addEventListener)mq.addEventListener("change",e=>{if(followSystem)setT
473
460
  document.getElementById("themeToggle").addEventListener("click",()=>setTheme(themeOf()==="dark"?"light":"dark",true));
474
461
 
475
462
  /* ---------- frames: lazy load, fit to width, grow with content ---------- */
476
- /* Cards serve their source over postMessage (works from file:// too, where the host
477
- cannot read the frame's DOM); the View Code UI is rendered here, below the box. */
478
- const CODE_ICON='<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 16 4-4-4-4"/><path d="m6 8-4 4 4 4"/><path d="m14.5 4-5 16"/></svg>';
479
- function buildCode(f,files){
480
- if(f.__code||!files||!files.length)return;
481
- const area=document.createElement("div");area.className="code-area";
482
- const btn=document.createElement("button");btn.className="code-btn";
483
- btn.innerHTML=CODE_ICON+'<span>View Code</span>';
484
- const panel=document.createElement("div");panel.className="code-panel";
485
- const head=document.createElement("div");head.className="code-head";
486
- const tabs=document.createElement("div");tabs.className="code-tabs";
487
- const fwb=document.createElement("div");fwb.className="code-fw";
488
- head.appendChild(tabs);head.appendChild(fwb);
489
- const body=document.createElement("div");body.className="code-body";
490
- const pre=document.createElement("pre");
491
- const copy=document.createElement("button");copy.className="copy-btn";copy.textContent="Copy";
492
- body.appendChild(pre);body.appendChild(copy);
493
- panel.appendChild(head);panel.appendChild(body);
494
- area.appendChild(btn);area.appendChild(panel);
495
- f.__section.appendChild(area);
496
- let framework="react",active=0,open=false;const cache={};
497
- files.forEach((t,i)=>{const b=document.createElement("button");b.className="code-tab";b.textContent=t.name;b.onclick=()=>{active=i;render();};tabs.appendChild(b);});
498
- [["react","React"],["vue","Vue 3"]].forEach(([k,lab])=>{const b=document.createElement("button");b.className="fw-btn";b.textContent=lab;b.onclick=()=>{framework=k;render();};fwb.appendChild(b);});
499
- const hidden=(t)=>(framework==="vue"&&t.reactOnly)||(framework==="react"&&t.vueOnly);
500
- function render(){
501
- if(hidden(files[active])){const k=files.findIndex(t=>!hidden(t));if(k>=0)active=k;}
502
- [...tabs.children].forEach((el,i)=>{el.classList.toggle("on",i===active);el.style.display=hidden(files[i])?"none":"";el.textContent=framework==="vue"?(files[i].vueName||files[i].name.replace(/\.jsx$/,".vue")):files[i].name;});
503
- [...fwb.children].forEach((el,i)=>el.classList.toggle("on",i===(framework==="react"?0:1)));
504
- const key=framework+"|"+files[active].path;
505
- if(cache[key]!=null)pre.textContent=cache[key];
506
- else{pre.textContent="Loading…";try{f.contentWindow.postMessage({kb:"src-get",path:files[active].path,framework},"*");}catch(e){}}
507
- }
508
- copy.onclick=()=>{const t=pre.textContent;try{navigator.clipboard.writeText(t);}catch(e){}copy.textContent="Copied";setTimeout(()=>{copy.textContent="Copy";},1200);};
509
- btn.onclick=()=>{open=!open;panel.classList.toggle("open",open);btn.querySelector("span").textContent=open?"Hide Code":"View Code";if(open)render();};
510
- f.__code={receive(m){cache[m.framework+"|"+m.path]=m.text;if(open&&files[active].path===m.path&&framework===m.framework)pre.textContent=m.text;}};
511
- }
463
+ /* Cards ask for the site theme over postMessage (works from file:// too). Usage code ("Show code") lives inside each card. */
512
464
  addEventListener("message",e=>{
513
465
  const m=e.data;if(!m||!m.kb)return;
514
466
  const f=[...document.querySelectorAll("iframe")].find(x=>{try{return x.contentWindow===e.source;}catch(err){return false;}});
515
467
  if(!f)return;
516
- if(m.kb==="src-manifest")buildCode(f,m.files);
517
- else if(m.kb==="src-text"&&f.__code)f.__code.receive(m);
518
- else if(m.kb==="theme-ask")pushTheme(f);
468
+ if(m.kb==="theme-ask")pushTheme(f);
519
469
  });
520
470
  function fit(f){
521
471
  const wrap=f.__wrap,avail=wrap.clientWidth;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyfunc/keybricks-ds",
3
- "version": "2.4.13",
3
+ "version": "2.5.1",
4
4
  "description": "Keybricks design system — design tokens, three size tiers and 121 UI components in React and Vue 3, source-only.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -45,7 +45,9 @@
45
45
  "guidelines",
46
46
  "changelog.js",
47
47
  "site-nav.js",
48
- "covers.js"
48
+ "covers.js",
49
+ "_ds_bundle.js",
50
+ "_src"
49
51
  ],
50
52
  "scripts": {
51
53
  "build:tokens": "node _scripts/build-tokens.mjs",
package/tokens/dark.css CHANGED
@@ -48,7 +48,7 @@ color-scheme:dark;
48
48
  --shadow-node:0 1px 3px 0 rgba(0,0,0,.6),0 1px 2px -1px rgba(0,0,0,.5);
49
49
  --shadow-selected:0 0 0 2px rgba(43,127,255,.32),0 1px 3px 0 rgba(0,0,0,.6);
50
50
  --shadow-menu:0 4px 16px rgba(0,0,0,.55),0 1px 3px rgba(0,0,0,.4);
51
- --shadow-modal:0 24px 64px rgba(0,0,0,.7),0 2px 8px rgba(0,0,0,.5);
51
+ --shadow-modal:0 24px 64px rgba(0,0,0,.7),0 2px 8px rgba(0,0,0,.5);--shadow-raised:0 1px 2px rgba(0,0,0,.4);--shadow-thumb:0 1px 2px rgba(0,0,0,.5);--shadow-drawer:-8px 0 24px rgba(0,0,0,.5);
52
52
  }
53
53
  [data-theme="dark"] body{background:var(--bg-app);color:var(--text-primary)}
54
54
  [data-theme="dark"] img[src$="keybricks-logo.png"]{filter:none}
@@ -47,3 +47,7 @@ summary:focus-visible,
47
47
  [role="tab"]:focus-visible,
48
48
  [role="switch"]:focus-visible,
49
49
  [role="treeitem"]:focus-visible{outline:2px solid var(--focus-border);outline-offset:2px}
50
+
51
+ /* A field box (Select, Combobox, DatePicker, MultiSelect …) is one line: its value text and tags truncate with an ellipsis
52
+ instead of spilling past the border. Icons and the chevron are svg or aria-hidden, so they are not affected. */
53
+ [data-field]>span:not([aria-hidden]),[role="combobox"]>span:not([aria-hidden]){min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
package/tokens/layout.css CHANGED
@@ -12,6 +12,9 @@
12
12
  --shadow-selected:0 0 0 2px var(--focus-ring),0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);
13
13
  --shadow-menu:0 4px 12px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.08);
14
14
  --shadow-modal:0 24px 64px rgba(0,0,0,.18),0 2px 8px rgba(0,0,0,.08);
15
+ --shadow-raised:0 1px 2px rgba(0,0,0,.08); /* a selected segment lifted off its track */
16
+ --shadow-thumb:0 1px 2px rgba(0,0,0,.15); /* slider thumb, switch knob */
17
+ --shadow-drawer:-8px 0 24px rgba(0,0,0,.12); /* right-hand drawer edge */
15
18
  --ring-surface:0 0 0 2px var(--bg-surface);
16
19
  --ring-focus:0 0 0 2px var(--bg-surface),0 0 0 4px var(--focus-border);
17
20
  /* Chrome sizes */
@@ -39,7 +39,8 @@
39
39
  [lang|="zh"]{--font-sans:'Inter','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Noto Sans SC',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}
40
40
  [lang|="ja"]{--font-sans:'Inter','Hiragino Kaku Gothic ProN','Hiragino Sans','Yu Gothic UI','Meiryo','Noto Sans JP',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}
41
41
  [lang|="ko"]{--font-sans:'Inter','Apple SD Gothic Neo','Malgun Gothic','Noto Sans KR',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}
42
- :lang(zh),:lang(ja),:lang(ko){font-family:var(--font-sans)}
42
+ /* Set the font only where a language is declared; plain text inherits it, so descendant icon and code fonts keep their own family (e.g. AG Grid selection icons). */
43
+ :where([lang|="zh"],[lang|="ja"],[lang|="ko"]){font-family:var(--font-sans)}
43
44
  [lang|="zh"],[lang|="ja"],[lang|="ko"]{--weight-medium:500;--weight-semibold:500;
44
45
  --text-glyph-min:12px;--text-2xs:12px;--text-xs:13px;--leading-xs:18px;--text-sm:13px;--leading-sm:18px;
45
46
  --tracking-2xs:0;--tracking-xs:0;--tracking-sm:0;--tracking-base:0;--tracking-md:0;--tracking-lg:0;--tracking-xl:0;--tracking-num:0;--tracking-2xl:0;--tracking-3xl:0;--tracking-4xl:0;--tracking-display:0}