anentrypoint-design 0.0.355 → 0.0.357

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.355",
3
+ "version": "0.0.357",
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",
@@ -73,6 +73,32 @@ export function useLongPress(targetEl, callback, { ms = 500 } = {}) {
73
73
  return () => { cancel(); evts.forEach(([k, fn]) => targetEl.removeEventListener(k, fn)); };
74
74
  }
75
75
 
76
+ // withBusy — run an async action with its triggering button disabled +
77
+ // busy-labelled, so a double-click/double-tap can't fire it twice and the
78
+ // user sees progress. Restores the button (label, disabled state,
79
+ // aria-busy) when the action settles, including on throw. Re-entry while
80
+ // already busy is dropped silently rather than queued. Mirrors docstudio's
81
+ // dom-busy.js withButtonBusy — agentgui's app.js has no equivalent anywhere,
82
+ // so every async-click handler (share/delete/retry/approve-deny) is
83
+ // currently unguarded against rapid repeat clicks firing the same mutating
84
+ // request twice.
85
+ export async function withBusy(btn, fn, busyLabel = '...') {
86
+ if (!btn) return fn();
87
+ if (btn.disabled) return; // already in flight -> drop the repeat
88
+ const prevHtml = btn.innerHTML;
89
+ const prevDisabled = btn.disabled;
90
+ btn.disabled = true;
91
+ btn.setAttribute('aria-busy', 'true');
92
+ if (busyLabel != null) btn.textContent = busyLabel;
93
+ try {
94
+ return await fn();
95
+ } finally {
96
+ btn.disabled = prevDisabled;
97
+ btn.removeAttribute('aria-busy');
98
+ btn.innerHTML = prevHtml;
99
+ }
100
+ }
101
+
76
102
  // Tooltip — single shared bubble appended to <body>.
77
103
  let _tipEl = null, _tipFloat = null, _tipTimer = null, _tipId = 0;
78
104
  function _hideTip() {
@@ -0,0 +1,78 @@
1
+ // SpreadsheetPreview — tabbed inline spreadsheet/CSV viewer, ported from
2
+ // docstudio's documents/xls-preview.js. The kit does no parsing (no SheetJS
3
+ // dependency): the host hands over an already-parsed `workbook` shape and
4
+ // this component only renders tabs + a table + loading/error/truncation
5
+ // states, reusing the existing Skeleton/Alert primitives rather than
6
+ // inventing new visual language.
7
+
8
+ import * as webjsx from '../../vendor/webjsx/index.js';
9
+ import { Skeleton, Alert } from './content.js';
10
+ const h = webjsx.createElement;
11
+
12
+ const DEFAULT_MAX_ROWS = 500;
13
+ const DEFAULT_MAX_COLS = 80;
14
+
15
+ // workbook: { sheetNames: string[], sheets: { [name]: string[][] } }
16
+ export function SpreadsheetPreview({
17
+ workbook, activeSheet, onSheetChange,
18
+ maxRows = DEFAULT_MAX_ROWS, maxCols = DEFAULT_MAX_COLS,
19
+ truncated, loading, error, errorActionLabel = 'retry', onErrorAction,
20
+ key,
21
+ } = {}) {
22
+ if (loading) {
23
+ return h('div', { key, class: 'ds-sheet-preview is-loading' },
24
+ Skeleton({ height: '1.8em', width: '40%', label: 'loading spreadsheet' }),
25
+ Skeleton({ height: '14em', width: '100%' }));
26
+ }
27
+
28
+ if (error) {
29
+ return h('div', { key, class: 'ds-sheet-preview is-error' },
30
+ Alert({
31
+ kind: 'error',
32
+ title: 'could not preview this file',
33
+ children: [String(error)],
34
+ }),
35
+ onErrorAction ? h('button', {
36
+ type: 'button', class: 'ds-sheet-preview-error-action', onclick: onErrorAction,
37
+ }, errorActionLabel) : null);
38
+ }
39
+
40
+ const sheetNames = (workbook && workbook.sheetNames) || [];
41
+ if (!sheetNames.length) {
42
+ return h('div', { key, class: 'ds-sheet-preview is-empty' }, 'no sheets found');
43
+ }
44
+
45
+ const active = activeSheet && sheetNames.includes(activeSheet) ? activeSheet : sheetNames[0];
46
+ const rowsRaw = (workbook.sheets && workbook.sheets[active]) || [];
47
+
48
+ const rowOverflow = rowsRaw.length > maxRows;
49
+ const rows = rowsRaw.slice(0, maxRows);
50
+ const colOverflow = rows.some((r) => Array.isArray(r) && r.length > maxCols);
51
+ const clampedRows = rows.map((r) => (Array.isArray(r) ? r.slice(0, maxCols) : r));
52
+ const isTruncated = truncated || rowOverflow || colOverflow;
53
+
54
+ const isNumeric = (v) => v !== '' && v != null && !isNaN(Number(v));
55
+
56
+ return h('div', { key, class: 'ds-sheet-preview' },
57
+ sheetNames.length > 1 ? h('div', {
58
+ class: 'ds-sheet-preview-tabbar', role: 'tablist', 'aria-label': 'sheets',
59
+ }, ...sheetNames.map((name) => h('button', {
60
+ key: name, type: 'button', role: 'tab',
61
+ 'aria-selected': name === active ? 'true' : 'false',
62
+ tabindex: name === active ? '0' : '-1',
63
+ class: 'ds-sheet-preview-tab' + (name === active ? ' is-active' : ''),
64
+ onclick: () => onSheetChange && onSheetChange(name),
65
+ }, name))) : null,
66
+ isTruncated ? h('div', { class: 'ds-sheet-preview-truncated', role: 'status' },
67
+ 'showing first ' + clampedRows.length + ' rows' + (colOverflow ? ' (columns truncated)' : '') + ' — full file has more data') : null,
68
+ h('div', { class: 'ds-sheet-preview-body' },
69
+ clampedRows.length === 0
70
+ ? h('div', { class: 'ds-sheet-preview-empty' }, 'empty sheet')
71
+ : h('table', { class: 'ds-sheet-preview-table' },
72
+ h('thead', {}, h('tr', {},
73
+ ...(clampedRows[0] || []).map((cell, i) => h('th', { key: i, scope: 'col' }, cell == null ? '' : String(cell))))),
74
+ h('tbody', {}, ...clampedRows.slice(1).map((row, ri) => h('tr', { key: ri },
75
+ ...row.map((cell, ci) => h('td', {
76
+ key: ci, class: isNumeric(cell) ? 'num' : null,
77
+ }, cell == null ? '' : String(cell)))))))));
78
+ }
package/src/components.js CHANGED
@@ -33,6 +33,8 @@ export {
33
33
 
34
34
  export { ContextPane } from './components/context-pane.js';
35
35
 
36
+ export { SpreadsheetPreview } from './components/spreadsheet-preview.js';
37
+
36
38
  export {
37
39
  fileGlyph, fmtFileSize,
38
40
  FileIcon, FileRow, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker,
@@ -90,7 +92,7 @@ export {
90
92
  export {
91
93
  Tooltip, Popover, Dropdown, useLongPress, useFloating,
92
94
  CommandPalette, EmojiPicker, BootOverlay, SettingsPopover,
93
- AuthModal, VideoLightbox, PermissionMenu, ApprovalPrompt
95
+ AuthModal, VideoLightbox, PermissionMenu, ApprovalPrompt, withBusy
94
96
  } from './components/overlay-primitives.js';
95
97
 
96
98
  export {
@@ -385,3 +385,46 @@
385
385
  .ds-avatar-sm { width: 24px; height: 24px; font-size: 10px; }
386
386
  .ds-avatar-md { width: 36px; height: 36px; font-size: 12px; }
387
387
  .ds-avatar-lg { width: 48px; height: 48px; font-size: 15px; }
388
+
389
+ /* SpreadsheetPreview — tabbed inline spreadsheet/CSV viewer (docstudio
390
+ xls-preview.js port). Tabs reuse the same neutral-at-rest pattern as
391
+ Table's sortable headers; the truncation banner is persistent and never
392
+ hides the table underneath. */
393
+ .ds-sheet-preview { display: flex; flex-direction: column; gap: var(--space-2, 8px); min-width: 0; }
394
+ .ds-sheet-preview-tabbar {
395
+ display: flex; gap: var(--space-1, 4px); overflow-x: auto; -webkit-overflow-scrolling: touch;
396
+ border-bottom: 1px solid var(--border, var(--bg-3));
397
+ }
398
+ .ds-sheet-preview-tab {
399
+ flex-shrink: 0; background: none; border: none; cursor: pointer;
400
+ padding: var(--space-1-5, 5px) var(--space-2-5, 10px);
401
+ font: inherit; color: var(--fg-3); border-bottom: 2px solid transparent;
402
+ }
403
+ .ds-sheet-preview-tab:hover { color: var(--fg); }
404
+ .ds-sheet-preview-tab:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; }
405
+ .ds-sheet-preview-tab.is-active { color: var(--fg); border-bottom-color: var(--accent-ink, var(--accent)); }
406
+ .ds-sheet-preview-truncated {
407
+ font-size: var(--fs-tiny, 12px); color: var(--fg-3);
408
+ padding: var(--space-1, 4px) var(--space-2, 8px);
409
+ background: var(--bg-2); border-radius: var(--r-1, 6px);
410
+ }
411
+ .ds-sheet-preview-body { overflow: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }
412
+ .ds-sheet-preview-table { min-width: 100%; border-collapse: collapse; font-size: var(--fs-sm, 13px); }
413
+ .ds-sheet-preview-table th, .ds-sheet-preview-table td {
414
+ padding: var(--space-1-5, 5px) var(--space-2, 8px);
415
+ border-bottom: 1px solid var(--border, var(--bg-3));
416
+ text-align: left; white-space: nowrap;
417
+ }
418
+ .ds-sheet-preview-table thead th {
419
+ position: sticky; top: 0; background: var(--bg-2); color: var(--fg-3);
420
+ font-weight: 600; z-index: 1;
421
+ }
422
+ .ds-sheet-preview-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
423
+ .ds-sheet-preview-empty { padding: var(--space-3, 16px); color: var(--fg-3); }
424
+ .ds-sheet-preview-error-action {
425
+ align-self: flex-start; background: none; border: 1px solid var(--border, var(--bg-3));
426
+ border-radius: var(--r-1, 6px); padding: var(--space-1, 4px) var(--space-2-5, 10px);
427
+ font: inherit; color: var(--fg); cursor: pointer;
428
+ }
429
+ .ds-sheet-preview-error-action:hover { background: var(--bg-2); }
430
+ .ds-sheet-preview-error-action:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 2px; }