anentrypoint-design 0.0.311 → 0.0.313
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/app-shell.css +231 -32
- package/chat.css +9 -0
- package/colors_and_type.css +23 -7
- package/community.css +11 -11
- package/dist/247420.css +328 -62
- package/dist/247420.js +15 -15
- package/package.json +1 -1
- package/src/components/agent-chat.js +2 -1
- package/src/components/chat.js +1 -3
- package/src/components/content.js +28 -12
- package/src/components/editor-primitives.js +27 -1
- package/src/components/files.js +19 -7
- package/src/components/sessions.js +14 -2
- package/src/components/shell.js +15 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.313",
|
|
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/
|
package/src/components/chat.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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,12 +531,26 @@ 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
|
-
|
|
535
|
-
|
|
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
|
});
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export function TextField({ label, value = '', type = 'text', placeholder = '', onInput, onChange, name, key, hint, multiline, rows = 4, maxLength, min, max, error, title, size = 'md', 'aria-label': ariaLabel, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy }) {
|
|
551
|
+
// size: 'sm' | 'md' | 'lg' — md is the base .ds-field control; sm/lg add a
|
|
552
|
+
// wrapper modifier that snaps the control height/padding/font to --ctl-*.
|
|
553
|
+
const sizeCls = size === 'sm' ? ' ds-field--sm' : (size === 'lg' ? ' ds-field--lg' : '');
|
|
540
554
|
const errorId = error != null ? ((key ? key : 'tf') + '-err') : null;
|
|
541
555
|
const describedBy = ariaDescribedBy || errorId || null;
|
|
542
556
|
const input = multiline
|
|
@@ -562,7 +576,7 @@ export function TextField({ label, value = '', type = 'text', placeholder = '',
|
|
|
562
576
|
oninput: onInput ? (e) => onInput(e.target.value, e) : null,
|
|
563
577
|
onchange: onChange ? (e) => onChange(e.target.value, e) : null
|
|
564
578
|
});
|
|
565
|
-
return h('label', { key, class: 'ds-field' },
|
|
579
|
+
return h('label', { key, class: 'ds-field' + sizeCls },
|
|
566
580
|
...[
|
|
567
581
|
label != null ? h('span', { key: 'l', class: 'ds-field-label' }, label) : null,
|
|
568
582
|
input,
|
|
@@ -573,7 +587,8 @@ export function TextField({ label, value = '', type = 'text', placeholder = '',
|
|
|
573
587
|
);
|
|
574
588
|
}
|
|
575
589
|
|
|
576
|
-
export function Select({ label, value = '', options = [], onChange, name, key, placeholder, hint, title, 'aria-label': ariaLabel }) {
|
|
590
|
+
export function Select({ label, value = '', options = [], onChange, name, key, placeholder, hint, title, size = 'md', 'aria-label': ariaLabel }) {
|
|
591
|
+
const sizeCls = size === 'sm' ? ' ds-field--sm' : (size === 'lg' ? ' ds-field--lg' : '');
|
|
577
592
|
const opts = [];
|
|
578
593
|
if (placeholder != null) opts.push(h('option', { key: '_ph', value: '', disabled: true, selected: value === '' || value == null }, placeholder));
|
|
579
594
|
for (const o of options) {
|
|
@@ -588,8 +603,9 @@ export function Select({ label, value = '', options = [], onChange, name, key, p
|
|
|
588
603
|
title,
|
|
589
604
|
onchange: onChange ? (e) => onChange(e.target.value, e) : null
|
|
590
605
|
}, ...opts);
|
|
591
|
-
if (label == null && hint == null) return select;
|
|
592
|
-
return h('label', { key, class: 'ds-field' },
|
|
606
|
+
if (label == null && hint == null && size === 'md') return select;
|
|
607
|
+
if (label == null && hint == null) return h('label', { key, class: 'ds-field' + sizeCls }, select);
|
|
608
|
+
return h('label', { key, class: 'ds-field' + sizeCls },
|
|
593
609
|
label != null ? h('span', { key: 'l', class: 'ds-field-label' }, label) : null,
|
|
594
610
|
select,
|
|
595
611
|
hint != null ? h('span', { key: 'h', class: 'ds-field-hint' }, hint) : null
|
|
@@ -53,7 +53,30 @@ export function Tabs({ items = [], active, onChange, children, 'aria-label': ari
|
|
|
53
53
|
if (btn) btn.focus();
|
|
54
54
|
});
|
|
55
55
|
};
|
|
56
|
-
|
|
56
|
+
// Position the sliding underline from the active tab's geometry. The ref is
|
|
57
|
+
// on the OUTER .ds-ep-tabs (position:relative) — the indicator is its child,
|
|
58
|
+
// NOT the flex head's (an abspos child of a horizontal flex row mis-sizes to
|
|
59
|
+
// 0 in Chromium). Runs on every render (ref fires after applyDiff) so a tab
|
|
60
|
+
// change re-measures; the CSS transition animates the move. left/width come
|
|
61
|
+
// from the active tab; top sits the bar on the head's bottom edge.
|
|
62
|
+
const positionSlider = (root) => {
|
|
63
|
+
if (!root) return;
|
|
64
|
+
const head = root.querySelector('.ds-ep-tabs-head');
|
|
65
|
+
const active = root.querySelector('.ds-ep-tab.active');
|
|
66
|
+
const ind = root.querySelector('.ds-ep-tab-indicator');
|
|
67
|
+
if (!head || !active || !ind) return;
|
|
68
|
+
// Size via left+right insets, NOT width: an abspos element's inline
|
|
69
|
+
// `width` computes to 0 in some flex-sibling layouts (Chromium), but
|
|
70
|
+
// left+right insets size reliably. left/right animate via the CSS
|
|
71
|
+
// transition on .ds-ep-tab-indicator.
|
|
72
|
+
const rootW = root.offsetWidth;
|
|
73
|
+
const l = active.offsetLeft, w = active.offsetWidth;
|
|
74
|
+
ind.style.left = l + 'px';
|
|
75
|
+
ind.style.right = Math.max(0, rootW - l - w) + 'px';
|
|
76
|
+
ind.style.top = (head.offsetTop + head.offsetHeight - 2) + 'px';
|
|
77
|
+
head.classList.add('has-slider');
|
|
78
|
+
};
|
|
79
|
+
return h('div', { class: 'ds-ep-tabs', ref: positionSlider },
|
|
57
80
|
h('div', { class: 'ds-ep-tabs-head', role: 'tablist', 'aria-label': ariaLabel || 'tabs' },
|
|
58
81
|
...items.map((it, idx) => h('button', {
|
|
59
82
|
key: it.id,
|
|
@@ -69,6 +92,9 @@ export function Tabs({ items = [], active, onChange, children, 'aria-label': ari
|
|
|
69
92
|
onkeydown: (e) => onTabKeyDown(e, idx)
|
|
70
93
|
}, it.label))
|
|
71
94
|
),
|
|
95
|
+
// The sliding underline — child of the outer column (see positionSlider).
|
|
96
|
+
// Keyed + decorative. Renders at 0-width until positioned (no-JS: hidden).
|
|
97
|
+
h('span', { key: '__ind', class: 'ds-ep-tab-indicator', 'aria-hidden': 'true' }),
|
|
72
98
|
h('div', {
|
|
73
99
|
class: 'ds-ep-tabs-body',
|
|
74
100
|
role: 'tabpanel',
|
package/src/components/files.js
CHANGED
|
@@ -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('
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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' },
|
package/src/components/shell.js
CHANGED
|
@@ -12,14 +12,22 @@ export function Brand({ name = '247420', leaf } = {}) {
|
|
|
12
12
|
leaf || null);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function Chip({ tone = '', children }) {
|
|
16
|
-
|
|
15
|
+
export function Chip({ tone = '', size = 'md', tag = false, children }) {
|
|
16
|
+
// size: 'sm' | 'md' | 'lg'; tag=true -> rectangular sentence-case variant
|
|
17
|
+
// for dense data (drops the all-caps pill). Both are orthogonal to tone.
|
|
18
|
+
const sizeCls = size === 'sm' ? ' chip--sm' : (size === 'lg' ? ' chip--lg' : '');
|
|
19
|
+
return h('span', { class: 'chip' + sizeCls + (tag ? ' chip--tag' : '') + (tone ? ' tone-' + tone : '') }, children);
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
export function Btn({ href, variant = 'default', children, onClick, 'aria-label': ariaLabel, primary, ghost, danger, disabled, class: className, key }) {
|
|
22
|
+
export function Btn({ href, variant = 'default', size = 'md', children, onClick, 'aria-label': ariaLabel, primary, ghost, danger, disabled, class: className, key }) {
|
|
20
23
|
// Support legacy primary/ghost props for backward compatibility, but prefer variant
|
|
21
24
|
const resolvedVariant = variant !== 'default' ? variant : (primary ? 'primary' : (ghost ? 'ghost' : (danger ? 'danger' : 'default')));
|
|
25
|
+
// size: 'sm' | 'md' | 'lg' — md is the base .btn rule (no class); sm/lg add a
|
|
26
|
+
// modifier that snaps height/padding/font to the --ctl-* ladder. Unknown
|
|
27
|
+
// sizes fall back to md so a typo never drops the button's base styling.
|
|
28
|
+
const sizeCls = size === 'sm' ? ' btn-sm' : (size === 'lg' ? ' btn-lg' : '');
|
|
22
29
|
const cls = (resolvedVariant === 'primary' ? 'btn-primary' : (resolvedVariant === 'ghost' ? 'btn-ghost' : (resolvedVariant === 'danger' ? 'btn-primary danger' : 'btn')))
|
|
30
|
+
+ sizeCls
|
|
23
31
|
+ (disabled ? ' is-disabled' : '')
|
|
24
32
|
+ (className ? ' ' + className : '');
|
|
25
33
|
const onclick = (e) => {
|
|
@@ -67,8 +75,10 @@ export function IconButton({ icon, onClick, title, size = 'base', variant = 'gho
|
|
|
67
75
|
}, Glyph({ children: icon, size }));
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
export function Badge({ children, variant = 'default', tone = 'neutral' }) {
|
|
71
|
-
|
|
78
|
+
export function Badge({ children, variant = 'default', tone = 'neutral', size = 'md' }) {
|
|
79
|
+
// size: 'sm' | 'md' | 'lg' — md is the base 18px badge.
|
|
80
|
+
const sizeCls = size === 'sm' ? ' ds-badge--sm' : (size === 'lg' ? ' ds-badge--lg' : '');
|
|
81
|
+
return h('span', { class: 'ds-badge ds-badge-' + variant + sizeCls + ' tone-' + tone }, children);
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
// Pill — plain non-interactive label chip for tag-like annotations (a phase
|