@keyfunc/keybricks-ds 2.4.13 → 2.5.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.
Files changed (92) hide show
  1. package/DEPLOY.md +4 -4
  2. package/INTEGRATION.md +17 -6
  3. package/changelog.js +10 -0
  4. package/components/_cards/accordion.card.html +1 -1
  5. package/components/_cards/action-chip.card.html +1 -1
  6. package/components/_cards/activity.card.html +1 -1
  7. package/components/_cards/alert.card.html +1 -1
  8. package/components/_cards/app-chrome.card.html +1 -1
  9. package/components/_cards/banner.card.html +1 -1
  10. package/components/_cards/breadcrumb.card.html +1 -1
  11. package/components/_cards/button-1-base.card.html +1 -1
  12. package/components/_cards/button-2-states.card.html +1 -1
  13. package/components/_cards/button-3-sizes-icons.card.html +1 -1
  14. package/components/_cards/button-4-group.card.html +1 -1
  15. package/components/_cards/cards-indicators.card.html +1 -1
  16. package/components/_cards/charts-1-sparkline.card.html +1 -1
  17. package/components/_cards/charts-10-gauge.card.html +1 -1
  18. package/components/_cards/charts-11-funnel.card.html +1 -1
  19. package/components/_cards/charts-12-treemap.card.html +1 -1
  20. package/components/_cards/charts-14-radar.card.html +1 -1
  21. package/components/_cards/charts-15-distribution.card.html +1 -1
  22. package/components/_cards/charts-16-pareto.card.html +1 -1
  23. package/components/_cards/charts-17-candlestick.card.html +1 -1
  24. package/components/_cards/charts-18-sankey.card.html +1 -1
  25. package/components/_cards/charts-19-chord.card.html +1 -1
  26. package/components/_cards/charts-2-line.card.html +1 -1
  27. package/components/_cards/charts-20-sunburst.card.html +1 -1
  28. package/components/_cards/charts-21-worldmap.card.html +1 -1
  29. package/components/_cards/charts-22-gantt.card.html +1 -1
  30. package/components/_cards/charts-23-bar3d.card.html +1 -1
  31. package/components/_cards/charts-24-pie3d.card.html +1 -1
  32. package/components/_cards/charts-25-surface3d.card.html +1 -1
  33. package/components/_cards/charts-26-scatter3d.card.html +1 -1
  34. package/components/_cards/charts-27-ribbon3d.card.html +1 -1
  35. package/components/_cards/charts-3-bar.card.html +1 -1
  36. package/components/_cards/charts-4-barlist.card.html +1 -1
  37. package/components/_cards/charts-5-donut.card.html +1 -1
  38. package/components/_cards/charts-6-scatter.card.html +1 -1
  39. package/components/_cards/charts-7-heatmap.card.html +1 -1
  40. package/components/_cards/charts-8-waterfall.card.html +1 -1
  41. package/components/_cards/charts-9-bullet.card.html +1 -1
  42. package/components/_cards/checkbox.card.html +1 -1
  43. package/components/_cards/codeblock.card.html +1 -1
  44. package/components/_cards/combobox.card.html +1 -1
  45. package/components/_cards/context-menu.card.html +1 -1
  46. package/components/_cards/date-picker.card.html +1 -1
  47. package/components/_cards/diffvalue.card.html +1 -1
  48. package/components/_cards/divider.card.html +1 -1
  49. package/components/_cards/drawer.card.html +1 -1
  50. package/components/_cards/dropdown-menu.card.html +1 -1
  51. package/components/_cards/dropzone.card.html +1 -1
  52. package/components/_cards/empty-state.card.html +1 -1
  53. package/components/_cards/icon-button.card.html +1 -1
  54. package/components/_cards/input.card.html +1 -1
  55. package/components/_cards/kbd.card.html +1 -1
  56. package/components/_cards/keyvalue.card.html +1 -1
  57. package/components/_cards/listrow.card.html +1 -1
  58. package/components/_cards/loading.card.html +1 -1
  59. package/components/_cards/log-viewer.card.html +1 -1
  60. package/components/_cards/multi-select.card.html +1 -1
  61. package/components/_cards/number-stepper.card.html +1 -1
  62. package/components/_cards/pagination.card.html +1 -1
  63. package/components/_cards/people.card.html +1 -1
  64. package/components/_cards/pipeline-node.card.html +1 -1
  65. package/components/_cards/pipeline-palette.card.html +1 -1
  66. package/components/_cards/radio.card.html +1 -1
  67. package/components/_cards/relative-time-picker.card.html +1 -1
  68. package/components/_cards/search.card.html +1 -1
  69. package/components/_cards/segmented-control.card.html +1 -1
  70. package/components/_cards/select.card.html +1 -1
  71. package/components/_cards/smart-table.card.html +1 -1
  72. package/components/_cards/spreadsheet.card.html +1 -1
  73. package/components/_cards/stepper.card.html +1 -1
  74. package/components/_cards/table.card.html +7 -2
  75. package/components/_cards/tabs.card.html +1 -1
  76. package/components/_cards/tag.card.html +1 -1
  77. package/components/_cards/textarea.card.html +1 -1
  78. package/components/_cards/timeline.card.html +1 -1
  79. package/components/_cards/toast.card.html +1 -1
  80. package/components/_cards/toggles.card.html +1 -1
  81. package/components/_cards/tooltip.card.html +1 -1
  82. package/components/_cards/tree.card.html +1 -1
  83. package/components/_shared/view-source.js +15 -185
  84. package/components/data/ListRow.d.ts +6 -3
  85. package/components/data/ListRow.jsx +21 -13
  86. package/components/data/ListRow.vue +25 -15
  87. package/components/data/Table.vue +6 -2
  88. package/components/feedback/Timeline.vue +4 -1
  89. package/components/navigation/Tree.vue +6 -0
  90. package/index.html +6 -56
  91. package/package.json +1 -1
  92. package/tokens/typography.css +2 -1
@@ -1,25 +1,35 @@
1
1
  <script setup>
2
- import { ref } from 'vue'
2
+ import { computed, getCurrentInstance, useSlots } from 'vue'
3
3
  import Icon from '../icons/Icon.vue'
4
4
  import IconTile from '../display/IconTile.vue'
5
- // Expandable workflow list row: chevron + bordered 32px icon tile + title/meta + right count.
6
- defineProps({ icon: { type: String, default: 'file-text' }, title: String, meta: String, trailing: [String, Number], expanded: Boolean, style: Object })
7
- defineEmits(['click'])
8
- const hover = ref(false)
5
+ // Expandable workflow list row: chevron + 32px hairline icon tile + title / meta + right count.
6
+ // The chevron shows when the row has content to expand (default slot); the row is a button only when it has a @click
7
+ // listener — focusable, Enter / Space, aria-expanded, hover wash from tokens/hover.css [data-row]. A row with neither is static.
8
+ // Rows without a chevron keep its 16px slot, so tiles line up in a mixed list. Expanded content starts under the title.
9
+ const props = defineProps({ icon: { type: String, default: 'file-text' }, title: String, meta: String, trailing: [String, Number], expanded: Boolean, style: Object })
10
+ const emit = defineEmits(['click'])
11
+ const slots = useSlots()
12
+ const inst = getCurrentInstance()
13
+ const hasContent = computed(() => !!slots.default)
14
+ const clickable = computed(() => !!(inst && inst.vnode.props && inst.vnode.props.onClick))
15
+ function onKey(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); emit('click', e) } }
16
+ const T_TITLE = { fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: 'var(--text-heading)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }
17
+ const T_META = { fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-regular)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', letterSpacing: 'var(--tracking-xs)', color: 'var(--text-secondary)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }
18
+ const T_TRAIL = { fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', fontVariantNumeric: 'tabular-nums', color: 'var(--text-secondary)', whiteSpace: 'nowrap', flexShrink: 0 }
9
19
  </script>
10
20
  <template>
11
21
  <div :style="{ borderBottom: '1px solid var(--border-card)', ...style }">
12
- <div @click="$emit('click')" @mouseenter="hover = true" @mouseleave="hover = false" :style="{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 16px', cursor: 'pointer', background: hover ? 'var(--bg-inset)' : 'transparent', transition: 'var(--t-hover)' }">
13
- <div style="display:flex;align-items:center;gap:12px">
14
- <span :style="{ color: 'var(--text-muted)', transform: expanded ? 'rotate(90deg)' : 'none', transition: 'transform .15s ease', display: 'inline-flex' }"><Icon name="chevron-right" :size="16" /></span>
15
- <IconTile :icon="icon" kind="row" />
16
- <div style="display:flex;flex-direction:column">
17
- <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-heading)">{{ title }}</span>
18
- <span v-if="meta" style="font-family:var(--font-sans);font-weight:var(--weight-regular);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary)">{{ meta }}</span>
19
- </div>
22
+ <div :data-row="clickable ? '' : undefined" :role="clickable ? 'button' : undefined" :tabindex="clickable ? 0 : undefined"
23
+ :aria-expanded="clickable && hasContent ? !!expanded : undefined" @click="clickable && emit('click', $event)" @keydown="clickable && onKey($event)"
24
+ :style="{ display: 'flex', alignItems: 'center', gap: '12px', padding: '12px var(--edge-pad)', cursor: clickable ? 'pointer' : 'default', background: 'var(--row-wash, transparent)', outlineOffset: '-2px', transition: 'var(--t-hover)' }">
25
+ <span aria-hidden="true" :style="{ width: '16px', flexShrink: 0, display: 'inline-flex', color: 'var(--text-muted)', transform: expanded ? 'rotate(90deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }"><Icon v-if="hasContent" name="chevron-right" :size="16" /></span>
26
+ <IconTile :icon="icon" kind="row" />
27
+ <div style="display:flex;flex-direction:column;min-width:0;flex:1">
28
+ <span :style="T_TITLE">{{ title }}</span>
29
+ <span v-if="meta" :style="T_META">{{ meta }}</span>
20
30
  </div>
21
- <span v-if="trailing" 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);font-variant-numeric:tabular-nums;color:var(--text-secondary)">{{ trailing }}</span>
31
+ <span v-if="trailing != null && trailing !== ''" :style="T_TRAIL">{{ trailing }}</span>
22
32
  </div>
23
- <div v-if="expanded" style="padding:0 16px 12px 72px"><slot /></div>
33
+ <div v-if="expanded && hasContent" style="padding:0 var(--edge-pad) 12px calc(var(--edge-pad) + 72px)"><slot /></div>
24
34
  </div>
25
35
  </template>
@@ -5,7 +5,11 @@ import Checkbox from '../forms/Checkbox.vue'
5
5
  import IconTile from '../display/IconTile.vue'
6
6
  import IconButton from '../buttons/IconButton.vue'
7
7
  import Icon from '../icons/Icon.vue'
8
- // Keybricks table. columns: [{key,label,width,minWidth,mono,code,twoLine,align,render}].
8
+ // A cell: a #cell-<key> slot ({ value, row, index }) when the page gives one; else column.render's result — text or a VNode from h() — or the raw value.
9
+ const Cell = (p) => p.v
10
+ Cell.props = ['v']
11
+ // Keybricks table. columns: [{key,label,width,minWidth,mono,code,twoLine,align,render}]. Custom cells: <template #cell-status="{ value, row, index }">…</template>,
12
+ // or render(value, row, index) returning text or a VNode (h()).
9
13
  // twoLine — the name column of a document list: icon tile, title over a description, optional mark ("favorite" = filled star), trailing ⋯
10
14
  // (@row-more(row, index, event)); @row-open(row, index, event) makes the tile and the title open the row (hand cursor, Enter / Space on the title).
11
15
  // tree — rows nest through `children` (or `hasChildren` for rows you load on demand); the twoLine column indents 24px per level with an
@@ -184,7 +188,7 @@ function cellPad(i) { return { ...(i === 0 && props.selectable ? { position: 're
184
188
  <tr v-else-if="pinInfo && pinInfo.style === 'labels' && pinInfo.grouped && pos === pinInfo.unStart"><td :colspan="columns.length || 1" :style="GROUP_CELL">{{ L.unavailable }} · {{ pinInfo.un }}</td></tr>
185
189
  <tr data-row="" :aria-level="tree ? item.level + 1 : undefined" :aria-expanded="item.kids ? item.open : undefined" :style="{ ...(isDis(item.r, item.ri) ? { '--row-wash': 'initial' } : null), height: 'var(--bar-h)', background: pinInfo && pinInfo.sel.has(item.ri) ? 'var(--row-wash, var(--bg-inset))' : 'var(--row-wash, transparent)', transition: 'var(--t-hover)', borderBottom: pinInfo && ((pinInfo.rest > 0 && pos === pinInfo.rest - 1) || (pinInfo.unStart > 0 && pos === pinInfo.unStart - 1)) ? '1px solid var(--border-control)' : bordered && pos === view.length - 1 ? 'none' : '1px solid var(--border-subtle)' }">
186
190
  <template v-for="{ r, ri } in [item]" :key="'r'">
187
- <td v-for="(c, i) in columns" :key="c.key" :style="{ ...cellPad(i), ...(c.twoLine ? { paddingTop: 'var(--title-cell-py)', paddingBottom: 'var(--title-cell-py)', maxWidth: 0 } : null), textAlign: c.align || 'left', whiteSpace: 'nowrap', ...(resizable ? { overflow: 'hidden', textOverflow: 'ellipsis' } : null), ...cellFont(c) }"><span v-if="selectable && i === 0" :style="checkSlot" :title="isDis(r, ri) && disabledReason ? disabledReason(r, ri) : undefined"><Checkbox :checked="selected.includes(ri)" :disabled="isDis(r, ri)" @change="v => emit('toggle', ri, v)" /></span><div v-if="c.twoLine" :style="{ display: 'flex', alignItems: 'center', minWidth: px(c.minWidth ?? 180), ...(treeCol === c ? { paddingLeft: (item.level * TREE_INDENT) + 'px' } : null) }"><span v-if="treeCol === c" :style="{ width: TREE_TOGGLE + 'px', flexShrink: 0, marginRight: (TREE_INDENT - TREE_TOGGLE) + 'px', display: 'inline-flex' }"><button v-if="item.kids" type="button" :aria-expanded="item.open" :aria-label="item.open ? L.collapse : L.expand" :title="item.open ? L.collapse : L.expand" @click.stop="toggleRow(item)" @mouseenter="hotToggle = item.key" @mouseleave="hotToggle = null" :style="{ width: TREE_TOGGLE + 'px', height: TREE_TOGGLE + 'px', padding: 0, border: 'none', borderRadius: 'var(--radius-sm)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', background: hotToggle === item.key ? 'var(--overlay-hover)' : 'transparent', color: hotToggle === item.key ? 'var(--text-body)' : 'var(--text-muted)', transition: 'var(--t-hover)' }"><Icon name="chevron-right" :size="16" :style="{ width: 'var(--control-icon)', height: 'var(--control-icon)', transform: item.open ? 'rotate(90deg)' : 'none', transition: 'transform .15s ease' }" /></button></span><div style="display:flex;align-items:center;gap:var(--title-cell-tile-gap);flex:1;min-width:0"><template v-for="t in [tl(c, r, ri)]" :key="0"><span v-if="t.icon" :style="{ display: 'inline-flex', flexShrink: 0, cursor: canOpen ? 'pointer' : undefined }" @click="e => canOpen && openRow(r, ri, e)"><IconTile :icon="t.icon" :hue="t.hue || 'neutral'" kind="row" :style="T_TILE" :icon-style="T_ICON" /></span><div style="display:flex;flex-direction:column;gap:var(--title-cell-gap);flex:1;min-width:0"><div style="display:flex;align-items:center;gap:4px;min-width:0"><span v-if="canOpen" role="button" tabindex="0" :style="{ ...T_TITLE, cursor: 'pointer' }" @click="e => openRow(r, ri, e)" @keydown="e => openKey(r, ri, e)">{{ c.render ? c.render(r[c.key], r, ri) : r[c.key] }}</span><span v-else :style="T_TITLE">{{ c.render ? c.render(r[c.key], r, ri) : r[c.key] }}</span><svg v-if="t.mark === 'favorite'" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linejoin="round" role="img" aria-label="Favorite" style="display:block;flex-shrink:0;color:var(--warning)"><path :d="STAR_FILLED" /></svg><span v-else-if="t.mark" role="img" :aria-label="t.mark.label" :style="{ display: 'inline-flex', flexShrink: 0, color: t.mark.color || 'var(--text-muted)' }"><Icon :name="t.mark.icon" :size="14" /></span></div><span v-if="t.desc" :style="T_DESC">{{ t.desc }}</span></div><IconButton v-if="t.more" icon="ellipsis" size="sm" muted :label="L.more" :title="L.more" @click="e => more(r, ri, e)" /></template></div></div><code v-else-if="c.code && (typeof r[c.key] === 'string' || typeof r[c.key] === 'number') && r[c.key] !== ''" :style="codeChip">{{ c.render ? c.render(r[c.key], r, ri) : r[c.key] }}</code><template v-else>{{ c.render ? c.render(r[c.key], r, ri) : r[c.key] }}</template></td>
191
+ <td v-for="(c, i) in columns" :key="c.key" :style="{ ...cellPad(i), ...(c.twoLine ? { paddingTop: 'var(--title-cell-py)', paddingBottom: 'var(--title-cell-py)', maxWidth: 0 } : null), textAlign: c.align || 'left', whiteSpace: 'nowrap', ...(resizable ? { overflow: 'hidden', textOverflow: 'ellipsis' } : null), ...cellFont(c) }"><span v-if="selectable && i === 0" :style="checkSlot" :title="isDis(r, ri) && disabledReason ? disabledReason(r, ri) : undefined"><Checkbox :checked="selected.includes(ri)" :disabled="isDis(r, ri)" @change="v => emit('toggle', ri, v)" /></span><div v-if="c.twoLine" :style="{ display: 'flex', alignItems: 'center', minWidth: px(c.minWidth ?? 180), ...(treeCol === c ? { paddingLeft: (item.level * TREE_INDENT) + 'px' } : null) }"><span v-if="treeCol === c" :style="{ width: TREE_TOGGLE + 'px', flexShrink: 0, marginRight: (TREE_INDENT - TREE_TOGGLE) + 'px', display: 'inline-flex' }"><button v-if="item.kids" type="button" :aria-expanded="item.open" :aria-label="item.open ? L.collapse : L.expand" :title="item.open ? L.collapse : L.expand" @click.stop="toggleRow(item)" @mouseenter="hotToggle = item.key" @mouseleave="hotToggle = null" :style="{ width: TREE_TOGGLE + 'px', height: TREE_TOGGLE + 'px', padding: 0, border: 'none', borderRadius: 'var(--radius-sm)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', background: hotToggle === item.key ? 'var(--overlay-hover)' : 'transparent', color: hotToggle === item.key ? 'var(--text-body)' : 'var(--text-muted)', transition: 'var(--t-hover)' }"><Icon name="chevron-right" :size="16" :style="{ width: 'var(--control-icon)', height: 'var(--control-icon)', transform: item.open ? 'rotate(90deg)' : 'none', transition: 'transform .15s ease' }" /></button></span><div style="display:flex;align-items:center;gap:var(--title-cell-tile-gap);flex:1;min-width:0"><template v-for="t in [tl(c, r, ri)]" :key="0"><span v-if="t.icon" :style="{ display: 'inline-flex', flexShrink: 0, cursor: canOpen ? 'pointer' : undefined }" @click="e => canOpen && openRow(r, ri, e)"><IconTile :icon="t.icon" :hue="t.hue || 'neutral'" kind="row" :style="T_TILE" :icon-style="T_ICON" /></span><div style="display:flex;flex-direction:column;gap:var(--title-cell-gap);flex:1;min-width:0"><div style="display:flex;align-items:center;gap:4px;min-width:0"><span v-if="canOpen" role="button" tabindex="0" :style="{ ...T_TITLE, cursor: 'pointer' }" @click="e => openRow(r, ri, e)" @keydown="e => openKey(r, ri, e)"><slot :name="'cell-' + c.key" :value="r[c.key]" :row="r" :index="ri"><Cell :v="c.render ? c.render(r[c.key], r, ri) : r[c.key]" /></slot></span><span v-else :style="T_TITLE"><slot :name="'cell-' + c.key" :value="r[c.key]" :row="r" :index="ri"><Cell :v="c.render ? c.render(r[c.key], r, ri) : r[c.key]" /></slot></span><svg v-if="t.mark === 'favorite'" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linejoin="round" role="img" aria-label="Favorite" style="display:block;flex-shrink:0;color:var(--warning)"><path :d="STAR_FILLED" /></svg><span v-else-if="t.mark" role="img" :aria-label="t.mark.label" :style="{ display: 'inline-flex', flexShrink: 0, color: t.mark.color || 'var(--text-muted)' }"><Icon :name="t.mark.icon" :size="14" /></span></div><span v-if="t.desc" :style="T_DESC">{{ t.desc }}</span></div><IconButton v-if="t.more" icon="ellipsis" size="sm" muted :label="L.more" :title="L.more" @click="e => more(r, ri, e)" /></template></div></div><code v-else-if="c.code && (typeof r[c.key] === 'string' || typeof r[c.key] === 'number') && r[c.key] !== ''" :style="codeChip"><slot :name="'cell-' + c.key" :value="r[c.key]" :row="r" :index="ri"><Cell :v="c.render ? c.render(r[c.key], r, ri) : r[c.key]" /></slot></code><template v-else><slot :name="'cell-' + c.key" :value="r[c.key]" :row="r" :index="ri"><Cell :v="c.render ? c.render(r[c.key], r, ri) : r[c.key]" /></slot></template></td>
188
192
  </template>
189
193
  </tr>
190
194
  </template>
@@ -3,6 +3,9 @@ import Icon from '../icons/Icon.vue'
3
3
  const tlTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }
4
4
  // Vertical activity timeline: outlined icon nodes on a hairline spine. items: [{icon,tone,title,meta,body}].
5
5
  defineProps({ items: { type: Array, default: () => [] }, style: Object })
6
+ // body may be text or a VNode (h(...)); a #body="{ item, index }" slot overrides it.
7
+ const Cell = (p) => p.v
8
+ Cell.props = ['v']
6
9
  </script>
7
10
  <template>
8
11
  <div :style="{ display: 'flex', flexDirection: 'column', ...style }">
@@ -14,7 +17,7 @@ defineProps({ items: { type: Array, default: () => [] }, style: Object })
14
17
  <div :style="{ display: 'flex', flexDirection: 'column', gap: '2px', paddingBottom: i < items.length - 1 ? '18px' : 0, minWidth: 0, paddingTop: '3px' }">
15
18
  <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-heading)">{{ it.title }}</span>
16
19
  <span v-if="it.meta" style="font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted)">{{ it.meta }}</span>
17
- <span v-if="it.body" style="font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-body);padding-top:4px">{{ it.body }}</span>
20
+ <span v-if="it.body" style="font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-body);padding-top:4px"><slot name="body" :item="it" :index="i"><Cell :v="it.body" /></slot></span>
18
21
  </div>
19
22
  </div>
20
23
  </div>
@@ -34,6 +34,8 @@ const props = defineProps({
34
34
  loadData: Function, rowActions: Function, style: Object,
35
35
  })
36
36
  const emit = defineEmits(['select', 'check', 'expand', 'drop'])
37
+ const Cell = (p) => p.v
38
+ Cell.props = ['v']
37
39
 
38
40
  const data = ref(JSON.parse(JSON.stringify(props.treeData || props.items)))
39
41
  // Compare by value: re-seeding on identity alone would wipe async-loaded children.
@@ -263,6 +265,8 @@ const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
263
265
  </span>
264
266
  <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
267
  <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>
268
+ <!-- row actions: #row-actions="{ node }" slot, or rowActions(node) returning a VNode; shown on hover / focus -->
269
+ <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
270
  </div>
267
271
  <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
272
  </div>
@@ -270,6 +274,8 @@ const dropAt = k => (drop.value && drop.value.key === k ? drop.value.pos : null)
270
274
  <style scoped>
271
275
  .kb-tree-row:not(.is-disabled):hover{background:var(--bg-hover)}
272
276
  .kb-tree-row:focus-visible{outline:2px solid var(--focus-border);outline-offset:-2px}
277
+ .kb-tree-actions{opacity:0;transition:var(--t-hover)}
278
+ .kb-tree-row:hover .kb-tree-actions,.kb-tree-row:focus-within .kb-tree-actions{opacity:1}
273
279
  .kb-tree-spin{animation:kb-tree-spin 900ms linear infinite}
274
280
  @keyframes kb-tree-spin{to{transform:rotate(360deg)}}
275
281
  </style>
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.0","设计系统 · v2.5.0","デザインシステム · v2.5.0"],
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.0",
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",
@@ -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}