anentrypoint-design 0.0.361 → 0.0.362
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/chat.css +27 -0
- package/dist/247420.css +108 -0
- package/dist/247420.js +25 -25
- package/package.json +1 -1
- package/src/components/chat.js +25 -0
- package/src/components/editor-primitives.js +72 -0
- package/src/components/overlay-primitives.js +91 -0
- package/src/components.js +6 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.362",
|
|
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",
|
package/src/components/chat.js
CHANGED
|
@@ -241,6 +241,31 @@ export function flashComposerNote(composerEl, text) {
|
|
|
241
241
|
showNext();
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
// ChatSuggestions — centered blank-thread heading + subtext + a wrapped row
|
|
245
|
+
// of prompt chips that fill the composer textarea on click and auto-dismiss
|
|
246
|
+
// on first send. Ported from docstudio's empty-state composer-priming CTA
|
|
247
|
+
// (distinct from a generic list EmptyState: this exists to seed a first
|
|
248
|
+
// message, not to describe an empty list). `onPick(prompt, item)` is the
|
|
249
|
+
// caller's single hook — the component does not touch the composer DOM
|
|
250
|
+
// itself, so the host decides how "fill the composer" actually happens.
|
|
251
|
+
// A rapid double-click on the same chip (or a click racing the first send)
|
|
252
|
+
// is guarded by a one-shot `_picked` flag: only the first click of any kind
|
|
253
|
+
// dispatches onPick, so the composer is never filled twice and the chips
|
|
254
|
+
// never reappear having already been "used".
|
|
255
|
+
export function ChatSuggestions({ heading = 'What can I help with?', subtext = '', suggestions = [] } = {}) {
|
|
256
|
+
let picked = false;
|
|
257
|
+
return h('div', { class: 'chat-suggestions', role: 'group', 'aria-label': heading },
|
|
258
|
+
h('h2', { class: 'chat-suggestions-heading' }, heading),
|
|
259
|
+
subtext ? h('p', { class: 'chat-suggestions-subtext' }, subtext) : null,
|
|
260
|
+
h('div', { class: 'chat-suggestions-list' },
|
|
261
|
+
...suggestions.map((s, i) => h('button', {
|
|
262
|
+
key: s.id || i, type: 'button', class: 'chat-suggestions-chip',
|
|
263
|
+
onclick: () => { if (picked) return; picked = true; s.onPick ? s.onPick(s) : null; }
|
|
264
|
+
}, s.label))
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
244
269
|
// Cached once per session: coarse-pointer (touch/no-hover) devices get a
|
|
245
270
|
// newline on Enter instead of send (mirrors the one-time-cache pattern
|
|
246
271
|
// editor-primitives.js uses for its own pointer/matchMedia checks).
|
|
@@ -918,3 +918,75 @@ export function Divider({ label, vertical = false, key } = {}) {
|
|
|
918
918
|
return h('div', { key, class: 'ds-ep-divider ds-ep-divider-labeled', role: 'separator' },
|
|
919
919
|
h('span', { class: 'ds-ep-divider-label' }, label));
|
|
920
920
|
}
|
|
921
|
+
|
|
922
|
+
// ---------------------------------------------------------------------------
|
|
923
|
+
// InfoRow / InfoSection / DiagnosticsPanel — static debug/system-info
|
|
924
|
+
// readouts: a bordered section of label + monospace-value rows. Ported from
|
|
925
|
+
// docstudio's diagnostics page (auth/streaming/service-worker state, recent
|
|
926
|
+
// client errors, environment facts). Distinct from PropertyGrid, which is
|
|
927
|
+
// for EDITABLE properties — these rows are read-only display, never inputs.
|
|
928
|
+
// `data == null` (not yet loaded) renders a loading placeholder row instead
|
|
929
|
+
// of an empty section, so a panel never flashes an empty bordered box before
|
|
930
|
+
// its first data arrives; `onRefresh` renders a trailing refresh button that
|
|
931
|
+
// reuses the section's own header row rather than shifting layout beneath it.
|
|
932
|
+
// ---------------------------------------------------------------------------
|
|
933
|
+
export function InfoRow({ label, value, key } = {}) {
|
|
934
|
+
return h('div', { key, class: 'ds-ep-inforow' },
|
|
935
|
+
h('span', { class: 'ds-ep-inforow-label' }, label),
|
|
936
|
+
h('span', { class: 'ds-ep-inforow-value' }, value == null || value === '' ? '—' : String(value)));
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
export function InfoSection({ title, rows, key } = {}) {
|
|
940
|
+
const children = [
|
|
941
|
+
title ? h('h3', { key: 'title', class: 'ds-ep-infosection-title' }, title) : null,
|
|
942
|
+
rows == null
|
|
943
|
+
? h('div', { key: 'body-loading', class: 'ds-ep-infosection-loading', role: 'status' }, 'Loading…')
|
|
944
|
+
: h('div', { key: 'body-rows', class: 'ds-ep-infosection-rows' }, ...rows.map((r, i) => InfoRow({ ...r, key: r.key != null ? r.key : i })))
|
|
945
|
+
].filter(Boolean);
|
|
946
|
+
return h('section', { key, class: 'ds-ep-infosection' }, ...children);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
export function DiagnosticsPanel({ title = 'Diagnostics', sections = [], onRefresh, refreshing = false, key } = {}) {
|
|
950
|
+
const headChildren = [
|
|
951
|
+
h('h2', { key: 'title', class: 'ds-ep-diagnostics-title' }, title),
|
|
952
|
+
onRefresh ? h('button', {
|
|
953
|
+
key: 'refresh', type: 'button', class: 'ds-ep-diagnostics-refresh', disabled: refreshing, 'aria-busy': refreshing ? 'true' : 'false',
|
|
954
|
+
onclick: () => onRefresh()
|
|
955
|
+
}, refreshing ? 'Refreshing…' : 'Refresh') : null
|
|
956
|
+
].filter(Boolean);
|
|
957
|
+
return h('div', { key, class: 'ds-ep-diagnostics' },
|
|
958
|
+
h('div', { key: 'head', class: 'ds-ep-diagnostics-head' }, ...headChildren),
|
|
959
|
+
...sections.map((s, i) => InfoSection({ ...s, key: s.key || i }))
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// ---------------------------------------------------------------------------
|
|
964
|
+
// BatchProgressLabel — sequential batch-operation progress readout: "label
|
|
965
|
+
// (i/n)" while in flight. Ported from docstudio's sequential upload badge
|
|
966
|
+
// and bulk-action progress button (both show a live count against a total
|
|
967
|
+
// while processing one item at a time). Pure display — the host owns the
|
|
968
|
+
// actual queue/loop; this only renders its current {done, total} state.
|
|
969
|
+
// done === 0 renders the bare label with no count suffix (nothing has
|
|
970
|
+
// happened yet); done === total renders as complete with no live-region
|
|
971
|
+
// churn once settled.
|
|
972
|
+
// ---------------------------------------------------------------------------
|
|
973
|
+
export function BatchProgressLabel({ label = 'Processing', done = 0, total = 0, key } = {}) {
|
|
974
|
+
const inFlight = total > 0 && done < total;
|
|
975
|
+
const suffix = total > 0 ? ` (${done}/${total})` : '';
|
|
976
|
+
return h('span', { key, class: 'ds-ep-batchprogress', role: 'status', 'aria-live': inFlight ? 'polite' : 'off' },
|
|
977
|
+
label + suffix);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// formatBatchOutcome — pure string helper for the aggregate toast shown once
|
|
981
|
+
// a batch settles: "N/total succeeded" plus a truncated failed-name list
|
|
982
|
+
// when any items failed, matching docstudio's attach-bar/bulk-action
|
|
983
|
+
// aggregation copy. Handles all three outcome shapes (all-succeed, all-fail,
|
|
984
|
+
// partial) and truncates a long failed-name list ("...and N more") instead
|
|
985
|
+
// of growing the toast unboundedly for a big batch.
|
|
986
|
+
export function formatBatchOutcome({ succeeded = 0, total = 0, failedNames = [], maxNames = 3 } = {}) {
|
|
987
|
+
if (total === 0) return '';
|
|
988
|
+
if (failedNames.length === 0) return `${succeeded}/${total} succeeded`;
|
|
989
|
+
const shown = failedNames.slice(0, maxNames).join(', ');
|
|
990
|
+
const more = failedNames.length > maxNames ? ` and ${failedNames.length - maxNames} more` : '';
|
|
991
|
+
return `${succeeded}/${total} succeeded; failed: ${shown}${more}`;
|
|
992
|
+
}
|
|
@@ -789,6 +789,97 @@ export function AuthModal({ mode = 'extension', error = '', busy = false, open =
|
|
|
789
789
|
);
|
|
790
790
|
}
|
|
791
791
|
|
|
792
|
+
// MenuButton — icon-trigger select menu: one option carries a checkmark
|
|
793
|
+
// (the active selection), roving keyboard nav mirrors Dropdown's own
|
|
794
|
+
// open/close/outside-click/typeahead wiring, plus a stale/unavailable
|
|
795
|
+
// per-item state that renders as a muted "unavailable — retry" row instead
|
|
796
|
+
// of a normal selectable item (ported from docstudio's model-picker menu,
|
|
797
|
+
// which shows a retry affordance when its option list fails to load).
|
|
798
|
+
// Zero-option and single-option lists degrade gracefully: an empty list
|
|
799
|
+
// renders a static "No options available" row (no crash, no keyboard trap,
|
|
800
|
+
// nothing focusable); roving nav on a single-option list simply refocuses
|
|
801
|
+
// the same item on every Arrow press (wrap-to-self), never throws.
|
|
802
|
+
export function MenuButton({ trigger, items = [], selected, onSelect, onRetry, placement = 'bottom-start', ariaLabel = 'Menu', emptyText = 'No options available' } = {}) {
|
|
803
|
+
let triggerEl = null, open = false, menuEl = null, floating = null, typeBuf = '', typeTimer = null;
|
|
804
|
+
const liveBtns = () => menuEl ? [...menuEl.querySelectorAll('[role="menuitemradio"]:not([aria-disabled="true"])')] : [];
|
|
805
|
+
const focusItem = (idx) => { const b = liveBtns(); if (!b.length) return; b[((idx % b.length) + b.length) % b.length].focus(); };
|
|
806
|
+
const onDown = (e) => { if (menuEl && menuEl.contains(e.target)) return; if (triggerEl && triggerEl.contains(e.target)) return; close(false); };
|
|
807
|
+
const close = (restore = true) => {
|
|
808
|
+
if (!open) return; open = false;
|
|
809
|
+
if (floating) { floating.dispose(); floating = null; }
|
|
810
|
+
if (menuEl && menuEl.parentNode) menuEl.parentNode.removeChild(menuEl);
|
|
811
|
+
menuEl = null;
|
|
812
|
+
document.removeEventListener('mousedown', onDown, true);
|
|
813
|
+
if (triggerEl) triggerEl.setAttribute('aria-expanded', 'false');
|
|
814
|
+
if (restore && triggerEl) triggerEl.focus();
|
|
815
|
+
};
|
|
816
|
+
const select = (it) => { if (it.disabled || it.unavailable) return; if (onSelect) onSelect(it.id, it); close(); };
|
|
817
|
+
const onMenuKey = (e) => {
|
|
818
|
+
const b = liveBtns(), idx = b.indexOf(document.activeElement);
|
|
819
|
+
if (e.key === 'Escape') { e.preventDefault(); close(); }
|
|
820
|
+
else if (e.key === 'ArrowDown') { e.preventDefault(); focusItem(idx + 1); }
|
|
821
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); focusItem(idx - 1); }
|
|
822
|
+
else if (e.key === 'Home') { e.preventDefault(); focusItem(0); }
|
|
823
|
+
else if (e.key === 'End') { e.preventDefault(); focusItem(b.length - 1); }
|
|
824
|
+
else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (idx >= 0) b[idx].click(); }
|
|
825
|
+
else if (e.key.length === 1 && /\S/.test(e.key)) {
|
|
826
|
+
typeBuf += e.key.toLowerCase();
|
|
827
|
+
if (typeTimer) clearTimeout(typeTimer);
|
|
828
|
+
typeTimer = setTimeout(() => { typeBuf = ''; }, 600);
|
|
829
|
+
const m = items.findIndex(it => !it.disabled && !it.unavailable && (it.label || '').toLowerCase().startsWith(typeBuf));
|
|
830
|
+
if (m >= 0) focusItem(items.slice(0, m).filter(it => !it.disabled && !it.unavailable).length);
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
const openMenu = (focusFirst = true) => {
|
|
834
|
+
if (open || !triggerEl) return;
|
|
835
|
+
open = true;
|
|
836
|
+
menuEl = document.createElement('div');
|
|
837
|
+
menuEl.className = 'ds-popover ov-menubutton-menu';
|
|
838
|
+
menuEl.setAttribute('role', 'menu');
|
|
839
|
+
menuEl.setAttribute('aria-label', ariaLabel);
|
|
840
|
+
menuEl.tabIndex = -1;
|
|
841
|
+
const tree = items.length
|
|
842
|
+
? h('div', { class: 'ov-menubutton-list' },
|
|
843
|
+
...items.map((it, i) => it.unavailable
|
|
844
|
+
? h('div', { key: it.id || i, class: 'ov-menubutton-item is-unavailable' },
|
|
845
|
+
h('span', { class: 'ov-menubutton-label' }, it.label || 'Unavailable'),
|
|
846
|
+
h('button', { type: 'button', class: 'ov-menubutton-retry', onclick: () => onRetry && onRetry(it.id, it) }, 'Retry')
|
|
847
|
+
)
|
|
848
|
+
: h('button', {
|
|
849
|
+
key: it.id || i, type: 'button', role: 'menuitemradio',
|
|
850
|
+
'aria-checked': it.id === selected ? 'true' : 'false',
|
|
851
|
+
class: 'ov-menubutton-item' + (it.disabled ? '' : ''),
|
|
852
|
+
'aria-disabled': it.disabled ? 'true' : 'false',
|
|
853
|
+
tabindex: '-1', onclick: () => select(it),
|
|
854
|
+
},
|
|
855
|
+
h('span', { class: 'ov-menubutton-check', 'aria-hidden': 'true' }, it.id === selected ? Icon('check', { size: 14 }) : ''),
|
|
856
|
+
h('span', { class: 'ov-menubutton-label' }, it.label)
|
|
857
|
+
)))
|
|
858
|
+
: h('div', { class: 'ov-menubutton-empty' }, emptyText);
|
|
859
|
+
webjsx.applyDiff(menuEl, tree);
|
|
860
|
+
document.body.appendChild(menuEl);
|
|
861
|
+
menuEl.addEventListener('keydown', onMenuKey);
|
|
862
|
+
floating = useFloating(triggerEl, menuEl, { placement, offset: FLOAT_OFFSET_DROPDOWN });
|
|
863
|
+
document.addEventListener('mousedown', onDown, true);
|
|
864
|
+
triggerEl.setAttribute('aria-expanded', 'true');
|
|
865
|
+
if (focusFirst && items.length) queueMicrotask(() => focusItem(0));
|
|
866
|
+
};
|
|
867
|
+
const onTrigClick = () => { if (open) close(false); else openMenu(true); };
|
|
868
|
+
const onTrigKey = (e) => { if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (!open) openMenu(true); else focusItem(0); } };
|
|
869
|
+
const refFn = (el) => {
|
|
870
|
+
if (!el || el._ovMenuButton) return;
|
|
871
|
+
el._ovMenuButton = true; triggerEl = el;
|
|
872
|
+
el.addEventListener('click', onTrigClick);
|
|
873
|
+
el.addEventListener('keydown', onTrigKey);
|
|
874
|
+
el.setAttribute('aria-haspopup', 'menu');
|
|
875
|
+
el.setAttribute('aria-expanded', 'false');
|
|
876
|
+
};
|
|
877
|
+
const child = (typeof trigger === 'function') ? trigger() : trigger;
|
|
878
|
+
return (child && child.type)
|
|
879
|
+
? webjsx.createElement(child.type, { ...(child.props || {}), ref: refFn }, ...(child.children || []))
|
|
880
|
+
: h('button', { type: 'button', class: 'ov-menubutton-trigger', ref: refFn }, child || 'Select');
|
|
881
|
+
}
|
|
882
|
+
|
|
792
883
|
// VideoLightbox — fullscreen video player overlay with backdrop dismiss.
|
|
793
884
|
export function VideoLightbox({ src, label = '', open = false, onClose } = {}) {
|
|
794
885
|
if (!open || !src) return null;
|
package/src/components.js
CHANGED
|
@@ -21,7 +21,7 @@ export {
|
|
|
21
21
|
|
|
22
22
|
export {
|
|
23
23
|
fmtBytes, renderInline, hasSelectionInside,
|
|
24
|
-
ChatMessage, ChatComposer, Chat, flashComposerNote,
|
|
24
|
+
ChatMessage, ChatComposer, Chat, flashComposerNote, ChatSuggestions,
|
|
25
25
|
AICAT_FACE, AICatPortrait, AICat
|
|
26
26
|
} from './components/chat.js';
|
|
27
27
|
|
|
@@ -95,13 +95,16 @@ export {
|
|
|
95
95
|
Collapse, CollapseGroup,
|
|
96
96
|
Divider,
|
|
97
97
|
useMediaQuery,
|
|
98
|
-
BP_SM, BP_MD, BP_LG, BP_XL
|
|
98
|
+
BP_SM, BP_MD, BP_LG, BP_XL,
|
|
99
|
+
InfoRow, InfoSection, DiagnosticsPanel,
|
|
100
|
+
BatchProgressLabel, formatBatchOutcome
|
|
99
101
|
} from './components/editor-primitives.js';
|
|
100
102
|
|
|
101
103
|
export {
|
|
102
104
|
Tooltip, Popover, Dropdown, useLongPress, useFloating,
|
|
103
105
|
CommandPalette, EmojiPicker, BootOverlay, SettingsPopover,
|
|
104
|
-
AuthModal, VideoLightbox, PermissionMenu, ApprovalPrompt, withBusy
|
|
106
|
+
AuthModal, VideoLightbox, PermissionMenu, ApprovalPrompt, withBusy,
|
|
107
|
+
MenuButton
|
|
105
108
|
} from './components/overlay-primitives.js';
|
|
106
109
|
|
|
107
110
|
export {
|