anentrypoint-design 0.0.208 → 0.0.210

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.
@@ -15,7 +15,7 @@ export function Chip({ tone = '', children }) {
15
15
  return h('span', { class: 'chip' + (tone ? ' tone-' + tone : '') }, children);
16
16
  }
17
17
 
18
- export function Btn({ href, variant = 'default', children, onClick, 'aria-label': ariaLabel, primary, ghost, danger, disabled, className }) {
18
+ export function Btn({ href, variant = 'default', children, onClick, 'aria-label': ariaLabel, primary, ghost, danger, disabled, className, key }) {
19
19
  // Support legacy primary/ghost props for backward compatibility, but prefer variant
20
20
  const resolvedVariant = variant !== 'default' ? variant : (primary ? 'primary' : (ghost ? 'ghost' : (danger ? 'danger' : 'default')));
21
21
  const cls = (resolvedVariant === 'primary' ? 'btn-primary' : (resolvedVariant === 'ghost' ? 'btn-ghost' : (resolvedVariant === 'danger' ? 'btn-primary danger' : 'btn')))
@@ -37,6 +37,7 @@ export function Btn({ href, variant = 'default', children, onClick, 'aria-label'
37
37
  const isLink = href != null && href !== '' && href !== '#';
38
38
  if (isLink) {
39
39
  return h('a', {
40
+ key,
40
41
  class: cls, href,
41
42
  'aria-label': ariaName,
42
43
  'aria-disabled': disabled ? 'true' : null,
@@ -45,6 +46,7 @@ export function Btn({ href, variant = 'default', children, onClick, 'aria-label'
45
46
  }, ...kids);
46
47
  }
47
48
  return h('button', {
49
+ key,
48
50
  type: 'button', class: cls,
49
51
  disabled: disabled ? true : null,
50
52
  'aria-label': ariaName,
@@ -159,6 +161,9 @@ const ICON_PATHS = {
159
161
  // Icon(); use innerHTML = iconMarkup(name). Keeps the icon paths upstream so
160
162
  // raw-DOM call sites never reintroduce decorative glyph literals.
161
163
  export function iconMarkup(name, { size = 16 } = {}) {
164
+ // Accept the props-object shape too - every sibling component takes a
165
+ // single object, so Icon({name}) is what the barrel trains consumers to try.
166
+ if (name && typeof name === 'object') ({ name, size = 16 } = name);
162
167
  const inner = ICON_PATHS[name];
163
168
  if (!inner) return '';
164
169
  return '<svg class="ds-icon ds-icon-' + name + '" width="' + size + '" height="' + size +
@@ -166,6 +171,7 @@ export function iconMarkup(name, { size = 16 } = {}) {
166
171
  ' stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + inner + '</svg>';
167
172
  }
168
173
  export function Icon(name, { size = 16 } = {}) {
174
+ if (name && typeof name === 'object') ({ name, size = 16 } = name);
169
175
  const inner = ICON_PATHS[name];
170
176
  if (!inner) return h('span', { class: 'glyph', 'aria-hidden': 'true' }, '');
171
177
  return h('svg', {
@@ -299,15 +305,60 @@ export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
299
305
  function toggleWs(which) {
300
306
  const shell = document.querySelector('.ws-shell');
301
307
  if (!shell) return;
302
- const cls = which === 'pane' ? 'ws-pane-collapsed' : 'ws-rail-collapsed';
308
+ const cls = which === 'pane' ? 'ws-pane-collapsed'
309
+ : which === 'sessions' ? 'ws-sessions-collapsed'
310
+ : 'ws-rail-collapsed';
303
311
  const nowCollapsed = shell.classList.toggle(cls);
304
- const btn = document.querySelector('.ws-' + which + '-toggle');
305
- if (btn) btn.setAttribute('aria-expanded', nowCollapsed ? 'false' : 'true');
312
+ document.querySelectorAll('.ws-' + which + '-toggle').forEach((btn) =>
313
+ btn.setAttribute('aria-expanded', nowCollapsed ? 'false' : 'true'));
306
314
  try {
307
315
  localStorage.setItem('ds.ws.' + which, nowCollapsed ? 'collapsed' : 'open');
308
316
  } catch (_) {}
309
317
  }
310
318
 
319
+ // Column resize: read the current rendered track width and write a clamped inline
320
+ // --ws-<col>-w on .ws-shell (inline overrides the fluid clamp base), persisted.
321
+ const WS_RESIZE_CLAMP = { rail: [60, 360], sessions: [200, 520], pane: [240, 560] };
322
+ function wsResize(col, dx) {
323
+ const shell = document.querySelector('.ws-shell');
324
+ if (!shell) return;
325
+ const track = shell.querySelector('.ws-' + col);
326
+ const cur = track ? track.getBoundingClientRect().width : 0;
327
+ const [lo, hi] = WS_RESIZE_CLAMP[col] || [120, 600];
328
+ const next = Math.max(lo, Math.min(hi, Math.round(cur + dx)));
329
+ shell.style.setProperty('--ws-' + col + '-w', next + 'px');
330
+ try { localStorage.setItem('ds.ws.w.' + col, String(next)); } catch (_) {}
331
+ }
332
+ function seedWsWidths(el) {
333
+ if (!el) return;
334
+ ['rail', 'sessions', 'pane'].forEach((col) => {
335
+ try {
336
+ const v = localStorage.getItem('ds.ws.w.' + col);
337
+ if (v && /^\d+$/.test(v)) el.style.setProperty('--ws-' + col + '-w', v + 'px');
338
+ } catch (_) {}
339
+ });
340
+ }
341
+ function WsResizer(col) {
342
+ const onKey = (e) => {
343
+ if (e.key === 'ArrowLeft') { e.preventDefault(); wsResize(col, -16); }
344
+ else if (e.key === 'ArrowRight') { e.preventDefault(); wsResize(col, 16); }
345
+ };
346
+ const onDown = (e) => {
347
+ e.preventDefault();
348
+ let lastX = e.clientX;
349
+ const move = (ev) => { const dx = ev.clientX - lastX; lastX = ev.clientX; wsResize(col, dx); };
350
+ const up = () => { document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up); document.body.style.cursor = ''; };
351
+ document.addEventListener('pointermove', move);
352
+ document.addEventListener('pointerup', up);
353
+ document.body.style.cursor = 'col-resize';
354
+ };
355
+ return h('div', {
356
+ class: 'ws-resizer ws-resizer-' + col, role: 'separator', tabindex: '0',
357
+ 'aria-orientation': 'vertical', 'aria-label': 'resize ' + col + ' column (arrow keys)',
358
+ onpointerdown: onDown, onkeydown: onKey,
359
+ });
360
+ }
361
+
311
362
  // Toggle a mobile WorkspaceShell DRAWER (sessions or pane). Distinct from the
312
363
  // desktop width-collapse (toggleWs): on mobile the columns are fixed overlays
313
364
  // revealed by .ws-sessions-open / .ws-pane-open. Opening one closes the other
@@ -388,7 +439,7 @@ export function WorkspaceShell({ rail, sessions, main, pane, crumb, status, narr
388
439
  + (((hasPane && paneIsCollapsed) || keepPaneTrack) ? ' ws-pane-collapsed' : '')
389
440
  + (hasSessions ? '' : ' ws-no-sessions')
390
441
  + (narrow ? ' narrow' : '');
391
- return h('div', { class: shellCls },
442
+ return h('div', { class: shellCls, ref: seedWsWidths },
392
443
  h('a', { href: '#ws-main', class: 'skip-link' }, 'skip to main content'),
393
444
  // Left rail column. Its own toggle collapses it to icon-only.
394
445
  h('nav', { class: 'ws-rail', role: 'navigation', 'aria-label': railLabel },
@@ -424,6 +475,13 @@ export function WorkspaceShell({ rail, sessions, main, pane, crumb, status, narr
424
475
  'aria-label': 'toggle conversations', 'aria-expanded': 'false',
425
476
  onclick: () => toggleWsDrawer('sessions'),
426
477
  }, Icon('thread')) : null,
478
+ // Desktop-only sessions collapse (reclaims its width for a
479
+ // full-width thread/grid). Hidden on mobile via CSS.
480
+ hasSessions ? h('button', {
481
+ class: 'ws-desktop-toggle ws-sessions-toggle', type: 'button',
482
+ 'aria-label': 'collapse conversations', title: 'collapse conversations',
483
+ 'aria-expanded': 'true', onclick: () => toggleWs('sessions'),
484
+ }, Icon('chevron-left')) : null,
427
485
  h('div', { class: 'ws-crumb-main' }, crumb),
428
486
  hasPane ? h('button', {
429
487
  class: 'ws-drawer-toggle ws-pane-drawer-toggle', type: 'button',
@@ -446,6 +504,10 @@ export function WorkspaceShell({ rail, sessions, main, pane, crumb, status, narr
446
504
  }, Icon(paneIsCollapsed ? 'chevron-left' : 'chevron-right')),
447
505
  pane)
448
506
  : null,
507
+ // Keyboard/pointer column resize handles (desktop only).
508
+ (!narrow && !railIsCollapsed) ? WsResizer('rail') : null,
509
+ (!narrow && hasSessions) ? WsResizer('sessions') : null,
510
+ (!narrow && hasPane && !paneIsCollapsed) ? WsResizer('pane') : null,
449
511
  );
450
512
  }
451
513
 
@@ -34,19 +34,26 @@ export function ThemeToggle({ compact = false, onChange } = {}) {
34
34
  const current = getTheme();
35
35
 
36
36
  if (compact) {
37
- const resolved = resolvedTheme();
38
- const label = current === 'auto' ? `auto (${resolved})` : (current === 'ink' ? 'dark' : 'light');
37
+ // Plain words only - 'ink'/'paper' are internal theme codenames a user
38
+ // never chose; the resolved scheme rides in the title, not the label.
39
+ const resolvedWord = resolvedTheme() === 'ink' ? 'dark' : 'light';
40
+ const word = current === 'auto' ? 'auto' : (current === 'ink' ? 'dark' : 'light');
41
+ const label = 'theme: ' + word;
39
42
  return h('button', {
40
43
  class: 'btn ds-theme-toggle',
41
44
  type: 'button',
42
- 'aria-label': 'theme: ' + label,
43
- title: 'theme: ' + label + ' — click to cycle',
45
+ 'aria-label': label,
46
+ title: label + (current === 'auto' ? ' (currently ' + resolvedWord + ')' : '') + ' — click to cycle',
44
47
  onclick: () => {
45
48
  const next = current === 'auto' ? 'paper' : (current === 'paper' ? 'ink' : 'auto');
46
49
  applyTheme(next);
47
50
  if (onChange) try { onChange(next); } catch {}
48
51
  }
49
- }, label);
52
+ },
53
+ // CSS-drawn half-disc so the control still reads as the theme switch
54
+ // when the label is hidden (icon-only rail strip).
55
+ h('span', { class: 'ds-theme-disc', 'aria-hidden': 'true' }),
56
+ h('span', { class: 'ds-theme-toggle-label' }, label));
50
57
  }
51
58
 
52
59
  return h('div', {