@kitn.ai/ui 0.18.0 → 0.18.2

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 (170) hide show
  1. package/dist/elements/agent-card.js +1 -1
  2. package/dist/elements/artifact.js +1 -1
  3. package/dist/elements/attachments.js +1 -1
  4. package/dist/elements/avatar.js +1 -1
  5. package/dist/elements/badge.js +1 -1
  6. package/dist/elements/button.js +1 -1
  7. package/dist/elements/card.js +1 -1
  8. package/dist/elements/cards.js +1 -1
  9. package/dist/elements/chain-of-thought.js +1 -1
  10. package/dist/elements/chat-scope-picker.js +1 -1
  11. package/dist/elements/chat-workspace.js +1 -1
  12. package/dist/elements/chat.js +1 -1
  13. package/dist/elements/checkpoint.js +1 -1
  14. package/dist/elements/choice.js +1 -1
  15. package/dist/elements/chunks/{Icon-DLLBeLG5.js → Icon-C2LTALdM.js} +1 -1
  16. package/dist/elements/chunks/{action-icons-UB4V5FNq.js → action-icons-Dtl8Ox5D.js} +1 -1
  17. package/dist/elements/chunks/arrow-left-GbBceGBE.js +1 -0
  18. package/dist/elements/chunks/{attachments-CgbErf1r.js → attachments-DLJcWCDV.js} +1 -1
  19. package/dist/elements/chunks/{badge-DTQpgB6N.js → badge-BTNZTODL.js} +1 -1
  20. package/dist/elements/chunks/{button-XDUM6lXs.js → button-DBDZebGr.js} +1 -1
  21. package/dist/elements/chunks/{card-CBlFZ-7I.js → card-YfCyI4fH.js} +1 -1
  22. package/dist/elements/chunks/{chat-thread-BIhmqwNL.js → chat-thread-Dz2qbzvI.js} +1 -1
  23. package/dist/elements/chunks/check-fhmRksq_.js +1 -0
  24. package/dist/elements/chunks/chevron-down-DcRg1aRU.js +1 -0
  25. package/dist/elements/chunks/chevron-right-ChzBcNJ-.js +1 -0
  26. package/dist/elements/chunks/circle-B4fTe9v6.js +1 -0
  27. package/dist/elements/chunks/circle-check-CJV-3Rem.js +1 -0
  28. package/dist/elements/chunks/{circle-x-DewgAEym.js → circle-x-pDtzlghQ.js} +1 -1
  29. package/dist/elements/chunks/{code-block-DedwxwFI.js → code-block-DZ6mz6vs.js} +1 -1
  30. package/dist/elements/chunks/{collapsible-FfgmTHtM.js → collapsible-CWUAShMC.js} +1 -1
  31. package/dist/elements/chunks/{composer-Cfbw5HKT.js → composer-BZyPU6gD.js} +1 -1
  32. package/dist/elements/chunks/{context-DwJmxlRE.js → context-Cx6eQA_G.js} +1 -1
  33. package/dist/elements/chunks/{conversation-list-DsCQMfPq.js → conversation-list-C4xTQSpT.js} +1 -1
  34. package/dist/elements/chunks/{default-input-BuqsoENC.js → default-input-BZcwyKcP.js} +1 -1
  35. package/dist/elements/chunks/define-CNSKFZCd.js +1 -0
  36. package/dist/elements/chunks/{disclosure-CwoAkpMH.js → disclosure-BcBs--Jf.js} +1 -1
  37. package/dist/elements/chunks/{download-DJ06G3Bz.js → download-BDQwcFav.js} +1 -1
  38. package/dist/elements/chunks/{dropdown-BFg8ri_R.js → dropdown-wgs8xCJZ.js} +1 -1
  39. package/dist/elements/chunks/{ellipsis-dlN6YvGw.js → ellipsis-CUiEJ6R1.js} +1 -1
  40. package/dist/elements/chunks/{external-link-DBX2lc_R.js → external-link-DJUmC7g2.js} +1 -1
  41. package/dist/elements/chunks/{file-text-HfwUx4F9.js → file-text-DOYfYvrh.js} +1 -1
  42. package/dist/elements/chunks/{file-tree-C6Toj71u.js → file-tree-DQzNQrbr.js} +1 -1
  43. package/dist/elements/chunks/{folder-MYuqa9Xp.js → folder-DBDUBANz.js} +1 -1
  44. package/dist/elements/chunks/{hover-card-gE7iyXdW.js → hover-card-BQGdGAQ3.js} +1 -1
  45. package/dist/elements/chunks/{icon-Vqz4ivrY.js → icon-CAyRqVrg.js} +1 -1
  46. package/dist/elements/chunks/{info-B_toFS0l.js → info-DBQl-m6e.js} +1 -1
  47. package/dist/elements/chunks/{input-gzbHYZvQ.js → input-DZD67EvW.js} +1 -1
  48. package/dist/elements/chunks/{kbd-n49c4KZm.js → kbd-DbyVrlnU.js} +1 -1
  49. package/dist/elements/chunks/{link-CKPs9yM0.js → link-vko7AttA.js} +1 -1
  50. package/dist/elements/chunks/{loader-q6MamGus.js → loader-Cg6rO8tQ.js} +1 -1
  51. package/dist/elements/chunks/{markdown-DBrcqq4S.js → markdown-n3Xbj3PR.js} +1 -1
  52. package/dist/elements/chunks/{message-CTqaYI0s.js → message-CgKytiSB.js} +1 -1
  53. package/dist/elements/chunks/{message-feedback-Cvzm03w0.js → message-feedback-Ds3-US0e.js} +1 -1
  54. package/dist/elements/chunks/message-square-MmRkvc-M.js +1 -0
  55. package/dist/elements/chunks/{minimize-2-l2klxP6K.js → minimize-2-3kCdE0q3.js} +1 -1
  56. package/dist/elements/chunks/{model-switcher-CmyjiHO1.js → model-switcher-BMtXGwLU.js} +1 -1
  57. package/dist/elements/chunks/{overlay-C8sOYqjT.js → overlay-B1-MBI6h.js} +1 -1
  58. package/dist/elements/chunks/{paperclip-42Ia7xHD.js → paperclip-DDEfmkHc.js} +1 -1
  59. package/dist/elements/chunks/{progress-bar-tzKMWAwO.js → progress-bar-Dwbiyv3s.js} +1 -1
  60. package/dist/elements/chunks/{prompt-suggestion-D3F6vP_A.js → prompt-suggestion-9lvgFcEa.js} +1 -1
  61. package/dist/elements/chunks/{reasoning-BJNmmyov.js → reasoning-DzHWsdAJ.js} +1 -1
  62. package/dist/elements/chunks/{resizable-BRsk19qI.js → resizable-Bq_eHxUm.js} +1 -1
  63. package/dist/elements/chunks/{rotate-cw-BgcggLHC.js → rotate-cw-jpisOdOV.js} +1 -1
  64. package/dist/elements/chunks/{scroll-area-CgGbFFbT.js → scroll-area-g5Ze3K6f.js} +1 -1
  65. package/dist/elements/chunks/{separator-8hM4GdHE.js → separator-Bmjc5XyX.js} +1 -1
  66. package/dist/elements/chunks/{settings-CaUIYOg-.js → settings-Cz1bMJP9.js} +1 -1
  67. package/dist/elements/chunks/{settings-group-CYjOVIZQ.js → settings-group-9imY3kRA.js} +1 -1
  68. package/dist/elements/chunks/{share-BTYM3Ddt.js → share-BdenF0wQ.js} +1 -1
  69. package/dist/elements/chunks/{skeleton-DsTYrPUe.js → skeleton-Pjc5jiaY.js} +1 -1
  70. package/dist/elements/chunks/{star-9R3LBQ9n.js → star-DY6z1UUH.js} +1 -1
  71. package/dist/elements/chunks/{store-CYl5Y0_u.js → store-th4VyJXM.js} +1 -1
  72. package/dist/elements/chunks/{text-shimmer-CaYGv8Se.js → text-shimmer-CEi-tlXr.js} +1 -1
  73. package/dist/elements/chunks/{textarea-NEr2SLcD.js → textarea-CboigarR.js} +1 -1
  74. package/dist/elements/chunks/{thumbs-up-DZYZkFfc.js → thumbs-up-BR33KLfA.js} +1 -1
  75. package/dist/elements/chunks/{toast-store-ClxVZRcc.js → toast-store-BEMda0fv.js} +1 -1
  76. package/dist/elements/chunks/{tool-CHZOmoe9.js → tool-TMued3WO.js} +1 -1
  77. package/dist/elements/chunks/{tooltip-WpHElELC.js → tooltip-Cft_Rxiw.js} +1 -1
  78. package/dist/elements/chunks/{triangle-alert-BlS80PUg.js → triangle-alert-B07fX3Q8.js} +1 -1
  79. package/dist/elements/chunks/{use-card-resolution-VSVoxDeJ.js → use-card-resolution-M36zVciB.js} +1 -1
  80. package/dist/elements/chunks/{video-DxTPZqT8.js → video-MKp3LmNT.js} +1 -1
  81. package/dist/elements/chunks/x-Ckwicf6g.js +1 -0
  82. package/dist/elements/coachmark.js +1 -1
  83. package/dist/elements/code-block.js +1 -1
  84. package/dist/elements/command.js +1 -1
  85. package/dist/elements/compare.js +1 -1
  86. package/dist/elements/composer.js +1 -1
  87. package/dist/elements/confirm-card.js +1 -1
  88. package/dist/elements/context-meter.js +1 -1
  89. package/dist/elements/conversation-list.js +1 -1
  90. package/dist/elements/dialog.js +1 -1
  91. package/dist/elements/editable-label.js +1 -1
  92. package/dist/elements/embed.js +1 -1
  93. package/dist/elements/empty.js +1 -1
  94. package/dist/elements/feedback-bar.js +1 -1
  95. package/dist/elements/file-tree.js +1 -1
  96. package/dist/elements/file-upload.js +1 -1
  97. package/dist/elements/form.js +1 -1
  98. package/dist/elements/hover-card.js +1 -1
  99. package/dist/elements/icon.js +1 -1
  100. package/dist/elements/image.js +1 -1
  101. package/dist/elements/input.js +1 -1
  102. package/dist/elements/kbd.js +1 -1
  103. package/dist/elements/link-preview.js +1 -1
  104. package/dist/elements/loader.js +1 -1
  105. package/dist/elements/markdown.js +1 -1
  106. package/dist/elements/menu.js +1 -1
  107. package/dist/elements/message-skills.js +1 -1
  108. package/dist/elements/message.js +1 -1
  109. package/dist/elements/model-switcher.js +1 -1
  110. package/dist/elements/nav.js +1 -1
  111. package/dist/elements/notice.js +1 -1
  112. package/dist/elements/pane-group.js +1 -1
  113. package/dist/elements/pane.js +1 -1
  114. package/dist/elements/popover.js +1 -1
  115. package/dist/elements/progress-bar.js +1 -1
  116. package/dist/elements/prompt-dock.js +1 -1
  117. package/dist/elements/prompt-input.js +1 -1
  118. package/dist/elements/prompt-suggestions.js +1 -1
  119. package/dist/elements/reasoning.js +1 -1
  120. package/dist/elements/remote.js +1 -1
  121. package/dist/elements/resizable.js +1 -1
  122. package/dist/elements/response-stream.js +1 -1
  123. package/dist/elements/screen.js +1 -1
  124. package/dist/elements/scroll-area.js +1 -1
  125. package/dist/elements/scroll-button.js +1 -1
  126. package/dist/elements/search.js +1 -1
  127. package/dist/elements/segmented.js +1 -1
  128. package/dist/elements/separator.js +1 -1
  129. package/dist/elements/setting-item.js +1 -1
  130. package/dist/elements/settings-group.js +1 -1
  131. package/dist/elements/skeleton.js +1 -1
  132. package/dist/elements/source.js +1 -1
  133. package/dist/elements/status.js +1 -1
  134. package/dist/elements/switch.js +1 -1
  135. package/dist/elements/tabs.js +1 -1
  136. package/dist/elements/tasks.js +1 -1
  137. package/dist/elements/text-shimmer.js +1 -1
  138. package/dist/elements/thinking-bar.js +1 -1
  139. package/dist/elements/toast.js +1 -1
  140. package/dist/elements/tool.js +1 -1
  141. package/dist/elements/tooltip.js +1 -1
  142. package/dist/elements/voice-input.js +1 -1
  143. package/dist/elements/voice-output.js +1 -1
  144. package/dist/index.js +4 -5
  145. package/dist/kai.es.js +1 -1
  146. package/dist/register-impl-DcjZDELj.js +141 -0
  147. package/dist/ui/pane-group.d.ts +4 -2
  148. package/package.json +9 -3
  149. package/src/agent-tooling/mcp/tools/debug.ts +1 -1
  150. package/src/components/coachmark.tsx +7 -3
  151. package/src/components/component-meta.json +8 -8
  152. package/src/components/tasks-card.tsx +0 -1
  153. package/src/elements/compiled.css +2 -2
  154. package/src/elements/define.tsx +40 -1
  155. package/src/elements/search.tsx +1 -1
  156. package/src/ui/agent-card.tsx +28 -22
  157. package/src/ui/command.tsx +5 -1
  158. package/src/ui/pane-grid.tsx +1 -0
  159. package/src/ui/pane-group.tsx +52 -41
  160. package/src/ui/pane.tsx +1 -1
  161. package/dist/elements/chunks/arrow-left-C-gwlUmx.js +0 -1
  162. package/dist/elements/chunks/check-hGUoVIKb.js +0 -1
  163. package/dist/elements/chunks/chevron-down-DDoaSUtO.js +0 -1
  164. package/dist/elements/chunks/chevron-right-CdGEUakB.js +0 -1
  165. package/dist/elements/chunks/circle-BKAZdmzb.js +0 -1
  166. package/dist/elements/chunks/circle-check-CmYG-y3I.js +0 -1
  167. package/dist/elements/chunks/define-rGsaWsLL.js +0 -1
  168. package/dist/elements/chunks/message-square-CUg9eRCi.js +0 -1
  169. package/dist/elements/chunks/x-C1qtndxn.js +0 -1
  170. package/dist/register-impl-CYWGkgbI.js +0 -141
@@ -137,13 +137,27 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
137
137
  }
138
138
  }
139
139
 
140
+ // Props that NAME a reflected global IDL accessor (today: only `role`, from
141
+ // ARIAMixin's Element.prototype.role). Unlike RESERVED (which throws), these are
142
+ // allowed as plain styling props, but they must NEVER reach the host as an
143
+ // attribute: kai-message uses role='user'|'assistant' to pick its layout, and
144
+ // those are not valid ARIA roles, so a reflected role="user" / role="assistant"
145
+ // fails axe's aria-roles rule. The native IDL setter reflects to an attribute
146
+ // whenever the property is set BEFORE connectedCallback (e.g. in a framework
147
+ // `ref`: `el.role = 'user'`), which is the window before component-register
148
+ // installs its own (non-reflecting) per-instance accessor in initializeProps. We
149
+ // shadow the native accessor on the element prototype with a plain non-reflecting
150
+ // one, so setting the property only STORES the value (component-register still
151
+ // reads it back for styling) and never touches a host attribute.
152
+ const NON_REFLECTING = ['role'];
153
+
140
154
  // Every element gets a `theme` property/attribute: 'light' | 'dark' | 'auto'
141
155
  // (default 'auto' = follow the OS `prefers-color-scheme`). It drives a `.dark`
142
156
  // class on an inner wrapper, which the injected kit CSS already styles — so dark
143
157
  // mode works in standalone Shadow-DOM usage with no token duplication.
144
158
  const defaults = { theme: 'auto', ...propDefaults };
145
159
 
146
- customElement(tag, defaults, (props: typeof defaults, options: { element: object }) => {
160
+ const Ctor = customElement(tag, defaults, (props: typeof defaults, options: { element: object }) => {
147
161
  const element = options.element as HTMLElement;
148
162
  let portalNode!: HTMLDivElement;
149
163
 
@@ -215,4 +229,29 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
215
229
  </>
216
230
  );
217
231
  });
232
+
233
+ // Shadow any reflected native IDL accessor this element re-uses as a prop (see
234
+ // NON_REFLECTING) with a plain, non-reflecting store on the element prototype.
235
+ // This must live on the prototype (not the instance) so it is already in place
236
+ // when the value is set before connectedCallback, the window in which the native
237
+ // setter would otherwise reflect it to a host attribute. component-register reads
238
+ // the property back during initializeProps to seed the styling prop, and later
239
+ // shadows this with its own (also non-reflecting) per-instance accessor.
240
+ const proto = (Ctor as unknown as { prototype?: object } | undefined)?.prototype;
241
+ if (proto) {
242
+ for (const key of NON_REFLECTING) {
243
+ if (!(key in defaults)) continue;
244
+ const store = Symbol(`kai-prop:${key}`);
245
+ Object.defineProperty(proto, key, {
246
+ get(this: Record<symbol, unknown>) {
247
+ return this[store];
248
+ },
249
+ set(this: Record<symbol, unknown>, value: unknown) {
250
+ this[store] = value;
251
+ },
252
+ enumerable: true,
253
+ configurable: true,
254
+ });
255
+ }
256
+ }
218
257
  }
@@ -157,7 +157,7 @@ defineWebComponent<Props, Events>('kai-search', {
157
157
  </button>
158
158
  </Show>
159
159
  <Show when={!value() && props.shortcut}>
160
- <Kbd keys={props.shortcut as string} size="sm" class="opacity-80" />
160
+ <Kbd keys={props.shortcut as string} size="sm" />
161
161
  </Show>
162
162
  </>
163
163
  );
@@ -115,27 +115,15 @@ export function AgentCard(props: AgentCardProps) {
115
115
  };
116
116
 
117
117
  return (
118
- // role="button" rather than a real <button> so the trailing kebab <button>
119
- // is a valid (non-nested) interactive element.
118
+ // A plain container (no interactive role): the agent name below is the real
119
+ // <button> activator, and the kebab is a separate sibling <button>, so neither
120
+ // interactive control is nested inside the other.
120
121
  <div
121
- role="button"
122
- tabindex="0"
123
- onClick={activate}
124
- onKeyDown={(e) => {
125
- // Only activate from the card itself, never from the nested kebab.
126
- if (e.currentTarget !== e.target) return;
127
- if (e.key === 'Enter' || e.key === ' ') {
128
- e.preventDefault();
129
- activate();
130
- }
131
- }}
132
- aria-current={local.active ? 'true' : undefined}
133
122
  data-active={local.active ? '' : undefined}
134
123
  data-needs-attention={local.needsAttention ? '' : undefined}
135
124
  class={cn(
136
- 'group flex w-full cursor-pointer items-center gap-3 rounded-lg border px-3 py-2.5 text-left',
137
- 'outline-none transition-[background-color,border-color,box-shadow]',
138
- 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',
125
+ 'group relative flex w-full cursor-pointer items-center gap-3 rounded-lg border px-3 py-2.5 text-left',
126
+ 'transition-[background-color,border-color,box-shadow]',
139
127
  local.active
140
128
  ? 'border-primary bg-accent'
141
129
  : 'border-border bg-surface hover:bg-accent',
@@ -174,9 +162,26 @@ export function AgentCard(props: AgentCardProps) {
174
162
  </Show>
175
163
  </span>
176
164
 
177
- <span class="truncate text-sm font-medium leading-tight text-foreground min-w-0 flex-1">
178
- {local.name}
179
- </span>
165
+ {/* The agent name is the card's primary action: a real <button> whose inset
166
+ ::before overlay stretches the click target over the whole card, so a click
167
+ anywhere promotes the agent. Being a real button it is natively focusable and
168
+ activates on Enter / Space, so no manual key handling is needed. The overlay
169
+ also carries the card's focus ring (rounded to match), keeping the focused
170
+ card visually identical to before. */}
171
+ <button
172
+ type="button"
173
+ onClick={activate}
174
+ aria-current={local.active ? 'true' : undefined}
175
+ class={cn(
176
+ 'min-w-0 flex-1 text-left outline-none',
177
+ "before:absolute before:inset-0 before:rounded-lg before:content-['']",
178
+ 'focus-visible:before:ring-2 focus-visible:before:ring-inset focus-visible:before:ring-ring',
179
+ )}
180
+ >
181
+ <span class="block truncate text-sm font-medium leading-tight text-foreground">
182
+ {local.name}
183
+ </span>
184
+ </button>
180
185
 
181
186
  <span class="flex shrink-0 items-center gap-2">
182
187
  <Show when={local.needsAttention}>
@@ -192,12 +197,13 @@ export function AgentCard(props: AgentCardProps) {
192
197
  part="menu"
193
198
  aria-label="More"
194
199
  onClick={(e) => {
195
- // Don't let the menu click also activate the card.
200
+ // The kebab sits above the name button's stretch overlay (relative z-10),
201
+ // so it opens its menu without activating the card; stop propagation too.
196
202
  e.stopPropagation();
197
203
  local.onMenu?.(e);
198
204
  }}
199
205
  class={cn(
200
- 'inline-flex size-7 shrink-0 items-center justify-center rounded-md',
206
+ 'relative z-10 inline-flex size-7 shrink-0 items-center justify-center rounded-md',
201
207
  'text-muted-foreground outline-none transition-colors',
202
208
  'hover:bg-surface-sunken hover:text-foreground',
203
209
  'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',
@@ -41,7 +41,11 @@ export function CommandList(props: CommandListProps): JSX.Element {
41
41
  const hasItems = () => props.groups.some((g) => g.items.length > 0);
42
42
 
43
43
  return (
44
- <div role="listbox" aria-label={props.ariaLabel ?? 'Command palette'} id={props.id}>
44
+ <div
45
+ role={hasItems() ? 'listbox' : undefined}
46
+ aria-label={hasItems() ? (props.ariaLabel ?? 'Command palette') : undefined}
47
+ id={props.id}
48
+ >
45
49
  <Show
46
50
  when={hasItems()}
47
51
  fallback={
@@ -108,6 +108,7 @@ export function PaneGrid(props: PaneGridProps) {
108
108
  // min-w-0 / min-h-0 let the grid shrink inside a flex/grid parent so its own
109
109
  // overflow:auto (not the parent) takes over once panes hit their minimums.
110
110
  class={cn('grid h-full w-full min-h-0 min-w-0 overflow-auto', props.class)}
111
+ tabindex={0}
111
112
  style={gridStyle()}
112
113
  >
113
114
  <For each={shown()}>{(pane) => pane}</For>
@@ -99,8 +99,10 @@ const TONE_TEXT: Record<PaneStatusTone, string> = {
99
99
  * renders `children` as the active pane's body and swaps it in response to
100
100
  * `onTabChange`. Selection-only — it never routes content itself.
101
101
  *
102
- * Accessibility: `role="tablist"` strip, `role="tab"` tabs with roving tabindex
103
- * and Arrow/Home/End navigation, the body as `role="tabpanel"`. Colors are all
102
+ * Accessibility: a `role="group"` ("Open panes") strip of real `<button>` tab
103
+ * activators (NOT `role="tab"`, so the strip can legally own the per-tab menu/close
104
+ * buttons) with roving tabindex + Arrow/Home/End navigation; the active activator is
105
+ * marked `aria-current="true"`, and the body is the `role="tabpanel"`. Colors are all
104
106
  * token-backed (surface / border / ring / tool-*), so it reads in light and dark.
105
107
  * The strip, each tab, and the body are exposed via `::part(tabs|tab|body)` for
106
108
  * the `kai-pane-group` facade. Give the group a bounded height for the body scroll.
@@ -166,57 +168,65 @@ export function PaneGroup(props: PaneGroupProps) {
166
168
  // The status word shows on the active tab, on hover, and always when the tab
167
169
  // needs attention or has errored.
168
170
  const alwaysWord = () => isActive() || !!tab().needsAttention || tone() === 'error';
171
+ // Outer wrapper is a plain div (no ARIA role) so the activator button and
172
+ // the menu/close buttons are siblings -- no nested-interactive violation.
169
173
  return (
170
174
  <div
171
- ref={(el) => { tabEls[tab().id] = el; }}
172
- part="tab"
173
- role="tab"
174
- tabindex={tab().id === rovingId() ? 0 : -1}
175
- aria-selected={isActive() ? 'true' : 'false'}
176
- data-active={isActive() ? '' : undefined}
177
- data-needs-attention={tab().needsAttention ? '' : undefined}
178
- onClick={() => select(tab().id)}
179
- onKeyDown={(e) => onKeyDown(e, tab())}
180
175
  class={cn(
181
- 'group/tab relative flex shrink-0 cursor-pointer items-center gap-1.5 rounded-md py-1 pl-1.5 pr-1 text-xs',
182
- 'outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',
176
+ 'group/tab relative flex shrink-0 items-center rounded-md text-xs',
183
177
  isActive()
184
178
  ? 'bg-background text-foreground shadow-sm ring-1 ring-border'
185
179
  : 'text-muted-foreground hover:bg-hover hover:text-foreground',
186
180
  tab().needsAttention && !isActive() && 'ring-1 ring-tool-amber/50',
187
181
  )}
182
+ data-active={isActive() ? '' : undefined}
183
+ data-needs-attention={tab().needsAttention ? '' : undefined}
188
184
  >
189
- {/* Numbered status badge: color = status, digit = the ⌥-jump number. */}
190
- <span class="relative flex size-[18px] shrink-0" aria-hidden="true">
191
- <Show when={tab().status?.pulse}>
192
- <span class={cn('absolute inset-0 animate-ping rounded opacity-60 motion-reduce:hidden', TONE_BG[tone()])} />
193
- </Show>
194
- <span
195
- class={cn(
196
- 'relative flex size-[18px] items-center justify-center rounded text-[11px] font-bold leading-none tabular-nums',
197
- TONE_BG[tone()],
198
- TONE_BADGE_FG[tone()],
199
- )}
200
- >
201
- {num()}
185
+ {/* Button-group activator (NOT role="tab") so the strip can legally own the
186
+ sibling menu/close buttons. aria-current marks the active pane. */}
187
+ <button
188
+ ref={(el) => { tabEls[tab().id] = el; }}
189
+ type="button"
190
+ part="tab"
191
+ tabindex={tab().id === rovingId() ? 0 : -1}
192
+ aria-current={isActive() ? 'true' : undefined}
193
+ onClick={() => select(tab().id)}
194
+ onKeyDown={(e) => onKeyDown(e, tab())}
195
+ class="flex cursor-pointer items-center gap-1.5 py-1 pl-1.5 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
196
+ >
197
+ {/* Numbered status badge: color = status, digit = the ⌥-jump number. */}
198
+ <span class="relative flex size-[18px] shrink-0" aria-hidden="true">
199
+ <Show when={tab().status?.pulse}>
200
+ <span class={cn('absolute inset-0 animate-ping rounded opacity-60 motion-reduce:hidden', TONE_BG[tone()])} />
201
+ </Show>
202
+ <span
203
+ class={cn(
204
+ 'relative flex size-[18px] items-center justify-center rounded text-[11px] font-bold leading-none tabular-nums',
205
+ TONE_BG[tone()],
206
+ TONE_BADGE_FG[tone()],
207
+ )}
208
+ >
209
+ {num()}
210
+ </span>
202
211
  </span>
203
- </span>
204
212
 
205
- <span class="max-w-[8rem] truncate font-medium">{tab().name}</span>
213
+ <span class="max-w-[8rem] truncate font-medium">{tab().name}</span>
206
214
 
207
- <Show when={tab().status?.label}>
208
- <span
209
- class={cn(
210
- 'truncate text-[10px] font-medium',
211
- TONE_TEXT[tone()],
212
- alwaysWord() ? 'inline' : 'hidden group-hover/tab:inline',
213
- )}
214
- >
215
- {tab().status!.label}
216
- </span>
217
- </Show>
215
+ <Show when={tab().status?.label}>
216
+ <span
217
+ class={cn(
218
+ 'truncate text-[10px] font-medium',
219
+ TONE_TEXT[tone()],
220
+ alwaysWord() ? 'inline' : 'hidden group-hover/tab:inline',
221
+ )}
222
+ >
223
+ {tab().status!.label}
224
+ </span>
225
+ </Show>
226
+ </button>
218
227
 
219
- <span class="ml-0.5 flex shrink-0 items-center">
228
+ {/* menu/close are siblings to the activator, not descendants -- no nested-interactive. */}
229
+ <span class="ml-0.5 flex shrink-0 items-center pr-1">
220
230
  <Show when={props.onTabMenu}>
221
231
  <button
222
232
  type="button"
@@ -266,7 +276,8 @@ export function PaneGroup(props: PaneGroupProps) {
266
276
  >
267
277
  <div
268
278
  part="tabs"
269
- role="tablist"
279
+ role="group"
280
+ aria-label="Open panes"
270
281
  class="flex shrink-0 items-center gap-1 overflow-x-auto border-b border-border px-1.5 py-1"
271
282
  >
272
283
  <For each={tabs()}>{(tab, i) => <TabItem tab={tab} index={i()} />}</For>
package/src/ui/pane.tsx CHANGED
@@ -179,7 +179,7 @@ export function Pane(props: PaneProps) {
179
179
  </div>
180
180
  </div>
181
181
 
182
- <div part="body" class="min-h-0 flex-1 overflow-y-auto">
182
+ <div part="body" class="min-h-0 flex-1 overflow-y-auto" tabindex={0}>
183
183
  {props.children}
184
184
  </div>
185
185
 
@@ -1 +0,0 @@
1
- import{I as o}from"./Icon-DLLBeLG5.js";import{c as m,m as r}from"./define-rGsaWsLL.js";var e=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],t=a=>m(o,r(a,{name:"ArrowLeft",iconNode:e})),p=t;export{p as a};
@@ -1 +0,0 @@
1
- import{I as m}from"./Icon-DLLBeLG5.js";import{c as o,m as r}from"./define-rGsaWsLL.js";var c=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],e=a=>o(m,r(a,{name:"Check",iconNode:c})),f=e;export{f as c};
@@ -1 +0,0 @@
1
- import{I as r}from"./Icon-DLLBeLG5.js";import{c as a,m}from"./define-rGsaWsLL.js";var e=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],n=o=>a(r,m(o,{name:"ChevronDown",iconNode:e})),p=n;export{p as c};
@@ -1 +0,0 @@
1
- import{I as o}from"./Icon-DLLBeLG5.js";import{c as a,m as r}from"./define-rGsaWsLL.js";var t=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],e=m=>a(o,r(m,{name:"ChevronRight",iconNode:t})),h=e;export{h as c};
@@ -1 +0,0 @@
1
- import{I as c}from"./Icon-DLLBeLG5.js";import{c as m,m as o}from"./define-rGsaWsLL.js";var a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],e=r=>m(c,o(r,{name:"Circle",iconNode:a})),i=e;export{i as c};
@@ -1 +0,0 @@
1
- import{I as c}from"./Icon-DLLBeLG5.js";import{c as r,m as a}from"./define-rGsaWsLL.js";var e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],o=m=>r(c,a(m,{name:"CircleCheck",iconNode:e})),i=o;export{i as c};