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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.361",
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",
@@ -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 {