anentrypoint-design 0.0.310 → 0.0.312

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.310",
3
+ "version": "0.0.312",
4
4
  "description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
5
5
  "type": "module",
6
6
  "main": "./dist/247420.js",
@@ -161,7 +161,7 @@ export function AgentChat(props = {}) {
161
161
  avatar, composerContext,
162
162
  followups = [], onFollowupClick,
163
163
  installHint, exportActions = [],
164
- onPasteFiles, onDropFiles,
164
+ onPasteFiles, onDropFiles, onEmoji,
165
165
  shownMessages, onShowEarlier,
166
166
  } = props;
167
167
 
@@ -335,6 +335,7 @@ export function AgentChat(props = {}) {
335
335
  // navigates away from a live session.
336
336
  onPasteFiles,
337
337
  onDropFiles,
338
+ onEmoji,
338
339
  });
339
340
 
340
341
  // Contextual follow-up chips below the last SETTLED assistant turn (claude.ai/
@@ -477,7 +477,7 @@ function flashComposerNote(composerEl, text) {
477
477
  note._dsNoteTimer = setTimeout(() => { note.remove(); }, 2600);
478
478
  }
479
479
 
480
- export function ChatComposer({ value, onInput, onSend, onAttach, onEmoji, onMenu, onCancel, busy, placeholder = 'message…', disabled, disabledReason, label, context, onPasteFiles, onDropFiles }) {
480
+ export function ChatComposer({ value, onInput, onSend, onEmoji, onCancel, busy, placeholder = 'message…', disabled, disabledReason, label, context, onPasteFiles, onDropFiles }) {
481
481
  // Keep a handle to the live textarea so send() reads the actual DOM value
482
482
  // (not the possibly-lagging `value` prop) and so we can sync the DOM value
483
483
  // only when it genuinely differs — re-applying `value` on every parent
@@ -644,9 +644,7 @@ export function ChatComposer({ value, onInput, onSend, onAttach, onEmoji, onMenu
644
644
  // (CSS) to save rows. Middot is kept product typography.
645
645
  h('div', { class: 'chat-composer-hint', 'aria-hidden': 'true' }, 'Enter to send · Shift+Enter for a new line'),
646
646
  h('div', { class: 'chat-composer-toolbar' },
647
- onAttach ? h('button', { type: 'button', class: 'composer-btn', onclick: (e) => { e.preventDefault(); onAttach(e); }, 'aria-label': 'attach file', title: 'attach file' }, Icon('paperclip')) : null,
648
647
  onEmoji ? h('button', { type: 'button', class: 'composer-btn', onclick: (e) => { e.preventDefault(); onEmoji(e); }, 'aria-label': 'emoji picker', title: 'emoji picker (Ctrl+;)' }, Icon('smile')) : null,
649
- onMenu ? h('button', { type: 'button', class: 'composer-btn', onclick: (e) => { e.preventDefault(); onMenu(e); }, 'aria-label': 'composer menu', title: 'more options' }, Icon('more-horizontal')) : null,
650
648
  busy && onCancel
651
649
  ? h('button', { type: 'button', class: 'send cancel', onclick: (e) => { e.preventDefault(); onCancel(e); }, 'aria-label': 'stop generating', title: 'stop generating (Esc)' }, Icon('square'))
652
650
  : h('button', { type: 'button', class: 'send', disabled: disabled || !(value && value.trim()), onclick: send,
@@ -521,9 +521,9 @@ export function PageHeader({ title, lede, eyebrow, right, compact, dense, id })
521
521
  );
522
522
  }
523
523
 
524
- export function SearchInput({ value = '', placeholder = 'search…', onInput, onSubmit, name = 'q', key, label }) {
525
- return h('input', {
526
- key,
524
+ export function SearchInput({ value = '', placeholder = 'search…', onInput, onSubmit, name = 'q', key, label, resultCount }) {
525
+ const input = h('input', {
526
+ key: 'i',
527
527
  type: 'search',
528
528
  name,
529
529
  class: 'ds-search-input',
@@ -531,9 +531,20 @@ export function SearchInput({ value = '', placeholder = 'search…', onInput, on
531
531
  'aria-label': label || placeholder,
532
532
  value,
533
533
  oninput: onInput ? (e) => onInput(e.target.value, e) : null,
534
- // IME guard: the Enter that commits a CJK composition must not submit.
535
- onkeydown: onSubmit ? (e) => { if (e.key === 'Enter' && !e.isComposing && e.keyCode !== 229) onSubmit(e.target.value, e); } : null
534
+ onkeydown: (e) => {
535
+ // Escape clears the field in place (stays focused) rather than
536
+ // falling through to whatever ancestor Escape handler exists.
537
+ if (e.key === 'Escape' && value) { e.preventDefault(); e.stopPropagation(); if (onInput) onInput('', e); return; }
538
+ // IME guard: the Enter that commits a CJK composition must not submit.
539
+ if (onSubmit && e.key === 'Enter' && !e.isComposing && e.keyCode !== 229) onSubmit(e.target.value, e);
540
+ }
536
541
  });
542
+ if (resultCount == null) return input;
543
+ // Visually-hidden live region announces the result count as it changes,
544
+ // without changing SearchInput's return shape for callers that don't pass it.
545
+ return h('span', { key, class: 'ds-search-input-wrap' },
546
+ input,
547
+ h('span', { key: 'cnt', class: 'sr-only', role: 'status', 'aria-live': 'polite' }, resultCount));
537
548
  }
538
549
 
539
550
  export function TextField({ label, value = '', type = 'text', placeholder = '', onInput, onChange, name, key, hint, multiline, rows = 4, maxLength, min, max, error, title, 'aria-label': ariaLabel, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy }) {
@@ -277,13 +277,25 @@ export function FileGrid({ files = [], onOpen, onAction, onUp, emptyText = 'No f
277
277
  // One toolbar baseline: filter + select-all + sort sit left, density is
278
278
  // pushed right by the spread. The filter used to be a separate right-aligned
279
279
  // strip ABOVE controls, giving two strips with conflicting alignment.
280
- const filterCtl = filter ? h('input', {
281
- key: 'filter',
282
- class: 'ds-file-filter-input', type: 'search',
283
- value: filter.value || '', placeholder: filter.placeholder || 'Filter files',
284
- 'aria-label': filter.placeholder || 'Filter files in this directory',
285
- oninput: (e) => filter.onInput && filter.onInput(e.target.value),
286
- }) : null;
280
+ const filterCtl = filter ? h('span', { key: 'filterwrap', class: 'ds-file-filter-wrap' },
281
+ h('input', {
282
+ key: 'filter',
283
+ class: 'ds-file-filter-input', type: 'search',
284
+ value: filter.value || '', placeholder: filter.placeholder || 'Filter files',
285
+ 'aria-label': filter.placeholder || 'Filter files in this directory',
286
+ oninput: (e) => filter.onInput && filter.onInput(e.target.value),
287
+ onkeydown: (e) => {
288
+ if (e.key === 'Escape' && filter.value) { e.preventDefault(); e.stopPropagation(); filter.onInput && filter.onInput(''); }
289
+ },
290
+ }),
291
+ // Announces the filtered count as the filter narrows the list, so a
292
+ // screen-reader user gets the same feedback a sighted user reads off
293
+ // the grid without having to re-scan it after every keystroke. `files`
294
+ // here is already the host's filter-applied set (see hasFilter above) -
295
+ // there is no separate pre-filter total available inside this component.
296
+ h('span', { key: 'filtercount', class: 'sr-only', role: 'status', 'aria-live': 'polite' },
297
+ hasFilter ? files.length + (files.length === 1 ? ' file' : ' files') + ' shown' : '')
298
+ ) : null;
287
299
  const leftKids = [filterCtl, selectAllCtl, head].filter(Boolean);
288
300
  const controlsKids = [
289
301
  ...leftKids,
@@ -35,7 +35,12 @@ export function fmtDuration(ms) {
35
35
  export function ConversationList({ sessions = [], selected, groups, search, caption,
36
36
  onSelect, onNew, newLabel = 'New chat',
37
37
  emptyText = 'No conversations yet', loading = false, error = null,
38
- loadingText = 'Loading conversations…' } = {}) {
38
+ loadingText = 'Loading conversations…',
39
+ // hasMore/onLoadMore: the rail's host truncates the underlying
40
+ // session list at some limit (a 200+ conversation user would
41
+ // otherwise never reach older sessions) - mirrors the History
42
+ // tab's existing "load N older" EventList pattern.
43
+ hasMore = false, onLoadMore, loadMoreLabel = 'load more conversations' } = {}) {
39
44
  const rowFor = (s, i) => h('div', {
40
45
  // Stable key: prefer sid, else position - a missing/duplicate sid would make
41
46
  // key undefined and crash webjsx applyDiff ("reading 'key'" of undefined).
@@ -97,7 +102,14 @@ export function ConversationList({ sessions = [], selected, groups, search, capt
97
102
  } else {
98
103
  inner = sessions.map(rowFor);
99
104
  }
100
- const body = h('div', { key: 'body', class: 'ds-session-list', role: 'listbox', 'aria-label': caption || 'Conversations' }, ...inner);
105
+ // The load-more row sits INSIDE the scrollable list body (not the outer
106
+ // .ds-sessions shell) so it scrolls with the rows it extends, matching
107
+ // where a user's eye already is after scrolling to the bottom of the rail.
108
+ const loadMoreRow = (hasMore && onLoadMore && sessions.length)
109
+ ? h('button', { key: 'loadmore', type: 'button', class: 'ds-session-loadmore', onclick: onLoadMore }, loadMoreLabel)
110
+ : null;
111
+ const body = h('div', { key: 'body', class: 'ds-session-list', role: 'listbox', 'aria-label': caption || 'Conversations' },
112
+ ...inner, loadMoreRow);
101
113
 
102
114
  return h('div', { class: 'ds-sessions' },
103
115
  h('div', { key: 'head', class: 'ds-session-head' },