@casualoffice/sheets 0.18.0 → 0.20.0

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.
Files changed (61) hide show
  1. package/dist/{api-DJhuCjhn.d.cts → api-cyO4EA4_.d.cts} +1 -1
  2. package/dist/{api-DJhuCjhn.d.ts → api-cyO4EA4_.d.ts} +1 -1
  3. package/dist/{attachCollab-BmCRT4V_.d.ts → attachCollab-BT4fVzxP.d.ts} +1 -1
  4. package/dist/{attachCollab-DIUQCCrq.d.cts → attachCollab-DHjb5XYK.d.cts} +1 -1
  5. package/dist/chrome.cjs +60 -3
  6. package/dist/chrome.cjs.map +1 -1
  7. package/dist/chrome.d.cts +3 -3
  8. package/dist/chrome.d.ts +3 -3
  9. package/dist/chrome.js +60 -3
  10. package/dist/chrome.js.map +1 -1
  11. package/dist/collab.d.cts +2 -2
  12. package/dist/collab.d.ts +2 -2
  13. package/dist/embed/embed-runtime.js +205 -161
  14. package/dist/{extensions-DZnqTEN2.d.ts → extensions-Cni7mbEm.d.ts} +1 -1
  15. package/dist/{extensions-FFqdLpk_.d.cts → extensions-DMmPnIYB.d.cts} +1 -1
  16. package/dist/index.cjs +6424 -1559
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +6409 -1530
  21. package/dist/index.js.map +1 -1
  22. package/dist/sheets.cjs +5426 -561
  23. package/dist/sheets.cjs.map +1 -1
  24. package/dist/sheets.d.cts +5 -5
  25. package/dist/sheets.d.ts +5 -5
  26. package/dist/sheets.js +5415 -536
  27. package/dist/sheets.js.map +1 -1
  28. package/package.json +8 -7
  29. package/src/charts/ChartContextMenu.tsx +264 -0
  30. package/src/charts/ChartLayer.tsx +333 -0
  31. package/src/charts/ChartOverlay.tsx +293 -0
  32. package/src/charts/ChartsPanel.tsx +232 -0
  33. package/src/charts/FormatChartDialog.tsx +419 -0
  34. package/src/charts/InsertChartDialog.tsx +478 -0
  35. package/src/charts/build-option.ts +476 -0
  36. package/src/charts/charts-context.tsx +205 -0
  37. package/src/charts/echarts-init.ts +58 -0
  38. package/src/charts/hit-test.ts +130 -0
  39. package/src/charts/insert-chart.ts +106 -0
  40. package/src/charts/naming.ts +38 -0
  41. package/src/charts/render-to-png.ts +117 -0
  42. package/src/charts/resources.ts +108 -0
  43. package/src/charts/types.ts +239 -0
  44. package/src/charts/univer-dom.ts +102 -0
  45. package/src/chrome/CommentsPanel.tsx +418 -0
  46. package/src/chrome/HistoryPanel.tsx +304 -0
  47. package/src/chrome/InsertPivotDialog.tsx +74 -2
  48. package/src/chrome/PanelHost.tsx +56 -0
  49. package/src/chrome/PanelRail.tsx +90 -0
  50. package/src/chrome/PivotFieldsPanel.tsx +1021 -0
  51. package/src/chrome/TablesPanel.tsx +283 -0
  52. package/src/chrome/Toolbar.tsx +7 -1
  53. package/src/chrome/panel-context.tsx +55 -0
  54. package/src/chrome/panel-registry.ts +48 -0
  55. package/src/chrome/panel-shell.tsx +151 -0
  56. package/src/pivots/apply.ts +139 -0
  57. package/src/pivots/compute.ts +604 -0
  58. package/src/pivots/fields-model.ts +267 -0
  59. package/src/pivots/types.ts +137 -0
  60. package/src/sheets/CasualSheets.tsx +61 -35
  61. package/src/sheets/api.ts +37 -2
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Copyright 2026 Casual Office
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License").
5
+ */
6
+
7
+ /**
8
+ * Comments side panel: lists the thread comments on the active sheet (active +
9
+ * resolved), navigates to a comment's cell on click, and resolves / reopens a
10
+ * thread inline. "Add comment" opens Univer's comment-on-cell modal (the same
11
+ * one as Review → New comment); the in-cell popup owns full reply threading.
12
+ *
13
+ * Ported from the standalone app's `shell/CommentsPanel`. The app-only collab
14
+ * stores it depended on — `collab/comment-authors` (avatar authorship),
15
+ * `collab/presence` (display name / initials) and `collab/comment-mentions`
16
+ * (@-mention "mentions you" badge) — DO NOT exist in the SDK and are dropped:
17
+ * the byline degrades to the in-band `personId` string carried on the comment
18
+ * model (no avatar, no color), and the mentions-me highlight is omitted. The
19
+ * `useUniverAPI()` / `useUI()` couplings are replaced by the `api` / `onClose`
20
+ * panel-host props, and the Univer facade is reached through `api.univer`.
21
+ */
22
+ import { useEffect, useMemo, useState, type CSSProperties } from 'react';
23
+ import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment';
24
+
25
+ import { ensurePluginByName } from '../univer';
26
+ import type { PanelComponentProps } from './extensions';
27
+ import { Icon } from './Icon';
28
+ import { PanelHeader, IconButton } from './panel-shell';
29
+
30
+ interface CommentRow {
31
+ id: string;
32
+ text: string;
33
+ ref: string;
34
+ author: string;
35
+ replies: number;
36
+ }
37
+
38
+ function cleanText(stream: string | undefined): string {
39
+ return (stream ?? '').replace(/[\r\n\t]+/g, ' ').trim() || '(empty comment)';
40
+ }
41
+
42
+ /**
43
+ * Active comments come straight off the facade (`getComments()` returns the
44
+ * cell-location index; resolved threads leave it, so they're read separately).
45
+ * Only root comments are listed — replies render under their root in the
46
+ * in-cell popup.
47
+ */
48
+ /* eslint-disable @typescript-eslint/no-explicit-any */
49
+ function readActive(univer: any): CommentRow[] {
50
+ const ws = univer?.getActiveWorkbook?.()?.getActiveSheet?.();
51
+ const all: any[] = ws?.getComments?.() ?? [];
52
+ const rows: CommentRow[] = [];
53
+ for (const c of all) {
54
+ try {
55
+ if (c.getIsRoot && !c.getIsRoot()) continue;
56
+ const data = c.getCommentData?.() ?? {};
57
+ let ref = '';
58
+ try {
59
+ ref = c.getRange?.()?.getA1Notation?.() ?? '';
60
+ } catch {
61
+ /* range no longer resolvable */
62
+ }
63
+ rows.push({
64
+ id: c.id ?? data.id ?? ref,
65
+ text: cleanText(data.text?.dataStream),
66
+ ref,
67
+ author: data.personId ?? '',
68
+ replies: c.getReplies?.()?.length ?? 0,
69
+ });
70
+ } catch {
71
+ /* skip malformed thread */
72
+ }
73
+ }
74
+ return rows;
75
+ }
76
+
77
+ /**
78
+ * Resolved comments have left the cell-location index, so `getComments()` no
79
+ * longer returns them. Read them straight from the model — the only path to a
80
+ * "resolved" view + reopen.
81
+ */
82
+ function readResolved(univer: any): CommentRow[] {
83
+ try {
84
+ const injector = (univer as { _injector?: { get(t: unknown): unknown } })?._injector;
85
+ const model = injector?.get?.(SheetsThreadCommentModel) as any;
86
+ const wb = univer?.getActiveWorkbook?.();
87
+ const ws = wb?.getActiveSheet?.();
88
+ if (!model?.getSubUnitAll || !wb || !ws) return [];
89
+ const unitId = wb.getId();
90
+ const subUnitId = ws.getSheetId?.() ?? ws.getId?.();
91
+ const all: any[] = model.getSubUnitAll(unitId, subUnitId) ?? [];
92
+ const rows: CommentRow[] = [];
93
+ for (const c of all) {
94
+ if (!c?.resolved) continue;
95
+ rows.push({
96
+ id: c.id,
97
+ text: cleanText(c.text?.dataStream),
98
+ ref: c.ref ?? '',
99
+ author: c.personId ?? '',
100
+ replies: (c.children ?? []).length,
101
+ });
102
+ }
103
+ return rows;
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+ /* eslint-enable @typescript-eslint/no-explicit-any */
109
+
110
+ const header: CSSProperties = {
111
+ display: 'flex',
112
+ alignItems: 'center',
113
+ gap: 8,
114
+ padding: '10px 12px',
115
+ borderBottom: '1px solid var(--cs-chrome-border, #edeff3)',
116
+ };
117
+
118
+ const rowCard: CSSProperties = {
119
+ border: '1px solid var(--cs-chrome-border, #edeff3)',
120
+ borderRadius: 8,
121
+ padding: 10,
122
+ display: 'flex',
123
+ alignItems: 'flex-start',
124
+ gap: 6,
125
+ };
126
+
127
+ const iconBtn: CSSProperties = {
128
+ border: 'none',
129
+ background: 'transparent',
130
+ cursor: 'pointer',
131
+ color: 'inherit',
132
+ padding: 0,
133
+ display: 'inline-flex',
134
+ alignItems: 'center',
135
+ };
136
+
137
+ function CommentCard({
138
+ row,
139
+ resolved,
140
+ onOpen,
141
+ onToggle,
142
+ }: {
143
+ row: CommentRow;
144
+ resolved: boolean;
145
+ onOpen: (r: CommentRow) => void;
146
+ onToggle: (id: string, value: boolean) => void;
147
+ }) {
148
+ return (
149
+ <li
150
+ data-testid={`cs-comments-panel-${resolved ? 'resolved-row' : 'row'}-${row.id}`}
151
+ style={{ ...rowCard, opacity: resolved ? 0.72 : 1 }}
152
+ >
153
+ <button
154
+ type="button"
155
+ onClick={() => onOpen(row)}
156
+ title={`Go to ${row.ref || 'comment'}`}
157
+ style={{
158
+ flex: 1,
159
+ minWidth: 0,
160
+ textAlign: 'left',
161
+ border: 'none',
162
+ background: 'transparent',
163
+ cursor: 'pointer',
164
+ color: 'inherit',
165
+ font: 'inherit',
166
+ display: 'flex',
167
+ flexDirection: 'column',
168
+ gap: 4,
169
+ }}
170
+ >
171
+ <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
172
+ <span style={{ fontWeight: 600 }}>{row.ref || 'Comment'}</span>
173
+ {row.author && <span style={{ opacity: 0.6, fontSize: 12 }}>{row.author}</span>}
174
+ {row.replies > 0 && (
175
+ <span
176
+ title={`${row.replies} replies`}
177
+ style={{
178
+ opacity: 0.6,
179
+ fontSize: 12,
180
+ display: 'inline-flex',
181
+ alignItems: 'center',
182
+ gap: 2,
183
+ }}
184
+ >
185
+ <Icon name="reply" size={13} />
186
+ {row.replies}
187
+ </span>
188
+ )}
189
+ </span>
190
+ <span
191
+ style={{
192
+ fontSize: 13,
193
+ overflow: 'hidden',
194
+ textOverflow: 'ellipsis',
195
+ display: '-webkit-box',
196
+ WebkitLineClamp: 2,
197
+ WebkitBoxOrient: 'vertical',
198
+ }}
199
+ >
200
+ {row.text}
201
+ </span>
202
+ </button>
203
+ <button
204
+ type="button"
205
+ data-testid={`cs-comments-panel-${resolved ? 'reopen' : 'resolve'}-${row.id}`}
206
+ aria-label={resolved ? 'Reopen comment' : 'Resolve comment'}
207
+ title={resolved ? 'Reopen comment' : 'Resolve comment'}
208
+ onClick={() => onToggle(row.id, !resolved)}
209
+ style={iconBtn}
210
+ >
211
+ <Icon name={resolved ? 'undo' : 'check_circle'} size={16} />
212
+ </button>
213
+ </li>
214
+ );
215
+ }
216
+
217
+ const REFRESH_CMDS = (id: string) =>
218
+ id.includes('comment') ||
219
+ id === 'sheet.operation.set-worksheet-activate' ||
220
+ id === 'doc.command-replace-snapshot';
221
+
222
+ export function CommentsPanel({ api, onClose }: PanelComponentProps) {
223
+ const univer = (api as { univer?: any }).univer; // eslint-disable-line @typescript-eslint/no-explicit-any
224
+ const [rows, setRows] = useState<CommentRow[]>([]);
225
+ const [resolved, setResolved] = useState<CommentRow[]>([]);
226
+ const [showResolved, setShowResolved] = useState(false);
227
+
228
+ useEffect(() => {
229
+ if (!univer) return;
230
+ let cancelled = false;
231
+ let disp: { dispose?: () => void } | undefined;
232
+ const read = () => {
233
+ if (cancelled) return;
234
+ setRows(readActive(univer));
235
+ setResolved(readResolved(univer));
236
+ };
237
+ // The thread-comment plugin is lazy — `getComments()` and
238
+ // SheetsThreadCommentModel only resolve once its group is registered.
239
+ void ensurePluginByName('threadComment').then(() => {
240
+ if (cancelled) return;
241
+ read();
242
+ disp = univer.addEvent(univer.Event.CommandExecuted, (e: { id?: string }) => {
243
+ if (e.id && REFRESH_CMDS(e.id)) read();
244
+ });
245
+ });
246
+ return () => {
247
+ cancelled = true;
248
+ disp?.dispose?.();
249
+ };
250
+ }, [univer]);
251
+
252
+ const empty = rows.length === 0 && resolved.length === 0;
253
+
254
+ const openThread = (r: CommentRow) => {
255
+ if (!univer || !r.ref) return;
256
+ try {
257
+ univer.getActiveWorkbook?.()?.getActiveSheet?.()?.getRange?.(r.ref)?.activate?.();
258
+ } catch {
259
+ /* range gone */
260
+ }
261
+ };
262
+
263
+ const setResolvedState = async (commentId: string, value: boolean) => {
264
+ if (!univer) return;
265
+ try {
266
+ const wb = univer.getActiveWorkbook?.();
267
+ const ws = wb?.getActiveSheet?.();
268
+ if (!wb || !ws) return;
269
+ const subUnitId = ws.getSheetId?.() ?? ws.getId?.();
270
+ await ensurePluginByName('threadComment');
271
+ univer.executeCommand('thread-comment.command.resolve-comment', {
272
+ unitId: wb.getId(),
273
+ subUnitId,
274
+ commentId,
275
+ resolved: value,
276
+ });
277
+ } catch {
278
+ /* command unavailable — plugin not loaded */
279
+ }
280
+ };
281
+
282
+ const addComment = async () => {
283
+ if (!univer) return;
284
+ await ensurePluginByName('threadComment');
285
+ univer.executeCommand('sheet.operation.show-comment-modal');
286
+ };
287
+
288
+ const handlers = useMemo(
289
+ () => ({ open: openThread, toggle: setResolvedState }),
290
+ // eslint-disable-next-line react-hooks/exhaustive-deps
291
+ [univer],
292
+ );
293
+
294
+ return (
295
+ <div
296
+ data-testid="cs-comments-panel"
297
+ style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
298
+ >
299
+ <PanelHeader
300
+ icon="forum"
301
+ title="Comments"
302
+ count={rows.length}
303
+ onClose={onClose}
304
+ actions={<IconButton name="add_comment" label="Add comment" onClick={addComment} />}
305
+ />
306
+
307
+ <div style={{ flex: 1, overflow: 'auto', padding: 12 }}>
308
+ {empty ? (
309
+ <div
310
+ data-testid="cs-comments-panel-empty"
311
+ style={{ textAlign: 'center', opacity: 0.75, padding: '24px 8px' }}
312
+ >
313
+ <Icon name="forum" size={40} style={{ opacity: 0.4 }} />
314
+ <div style={{ fontWeight: 600, marginTop: 8 }}>No comments yet</div>
315
+ <div style={{ fontSize: 13, marginTop: 4 }}>
316
+ Select a cell and add a comment to start a discussion — or use{' '}
317
+ <strong>Review → New comment</strong>.
318
+ </div>
319
+ <button
320
+ type="button"
321
+ data-testid="cs-comments-panel-empty-cta"
322
+ disabled={!univer}
323
+ onClick={addComment}
324
+ style={{
325
+ marginTop: 12,
326
+ padding: '6px 14px',
327
+ borderRadius: 6,
328
+ border: 'none',
329
+ cursor: univer ? 'pointer' : 'default',
330
+ background: 'var(--cs-chrome-active-fg, #0e7490)',
331
+ color: '#fff',
332
+ font: 'inherit',
333
+ fontWeight: 600,
334
+ }}
335
+ >
336
+ Add comment
337
+ </button>
338
+ </div>
339
+ ) : (
340
+ <>
341
+ {rows.length > 0 && (
342
+ <ul
343
+ style={{
344
+ listStyle: 'none',
345
+ margin: 0,
346
+ padding: 0,
347
+ display: 'flex',
348
+ flexDirection: 'column',
349
+ gap: 8,
350
+ }}
351
+ >
352
+ {rows.map((r) => (
353
+ <CommentCard
354
+ key={r.id}
355
+ row={r}
356
+ resolved={false}
357
+ onOpen={handlers.open}
358
+ onToggle={handlers.toggle}
359
+ />
360
+ ))}
361
+ </ul>
362
+ )}
363
+
364
+ {resolved.length > 0 && (
365
+ <div style={{ marginTop: rows.length > 0 ? 12 : 0 }}>
366
+ <button
367
+ type="button"
368
+ data-testid="cs-comments-panel-resolved-toggle"
369
+ aria-expanded={showResolved}
370
+ onClick={() => setShowResolved((v) => !v)}
371
+ style={{
372
+ display: 'flex',
373
+ alignItems: 'center',
374
+ gap: 4,
375
+ width: '100%',
376
+ border: 'none',
377
+ background: 'transparent',
378
+ cursor: 'pointer',
379
+ color: 'inherit',
380
+ font: 'inherit',
381
+ fontWeight: 600,
382
+ padding: '4px 0',
383
+ }}
384
+ >
385
+ <Icon name={showResolved ? 'expand_more' : 'chevron_right'} size={16} />
386
+ Resolved
387
+ <span style={{ opacity: 0.6, fontSize: 12 }}>{resolved.length}</span>
388
+ </button>
389
+ {showResolved && (
390
+ <ul
391
+ style={{
392
+ listStyle: 'none',
393
+ margin: '8px 0 0',
394
+ padding: 0,
395
+ display: 'flex',
396
+ flexDirection: 'column',
397
+ gap: 8,
398
+ }}
399
+ >
400
+ {resolved.map((r) => (
401
+ <CommentCard
402
+ key={r.id}
403
+ row={r}
404
+ resolved
405
+ onOpen={handlers.open}
406
+ onToggle={handlers.toggle}
407
+ />
408
+ ))}
409
+ </ul>
410
+ )}
411
+ </div>
412
+ )}
413
+ </>
414
+ )}
415
+ </div>
416
+ </div>
417
+ );
418
+ }