@pixerate/editor-svelte 0.23.0 → 0.25.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.
- package/README.md +4 -2
- package/dist/ExportDocxButton.svelte +48 -0
- package/dist/ExportDocxButton.svelte.d.ts +13 -0
- package/dist/ExportDocxButton.svelte.d.ts.map +1 -0
- package/dist/StylesGallery.svelte +169 -0
- package/dist/StylesGallery.svelte.d.ts +27 -0
- package/dist/StylesGallery.svelte.d.ts.map +1 -0
- package/dist/canvas/AnimationTimeline.svelte +217 -0
- package/dist/canvas/AnimationTimeline.svelte.d.ts +20 -0
- package/dist/canvas/AnimationTimeline.svelte.d.ts.map +1 -0
- package/dist/canvas/CanvasComponentsPanel.svelte +73 -0
- package/dist/canvas/CanvasComponentsPanel.svelte.d.ts +13 -0
- package/dist/canvas/CanvasComponentsPanel.svelte.d.ts.map +1 -0
- package/dist/canvas/CanvasInstanceInspector.svelte +136 -0
- package/dist/canvas/CanvasInstanceInspector.svelte.d.ts +16 -0
- package/dist/canvas/CanvasInstanceInspector.svelte.d.ts.map +1 -0
- package/dist/canvas/TokenBindingPicker.svelte +93 -0
- package/dist/canvas/TokenBindingPicker.svelte.d.ts +20 -0
- package/dist/canvas/TokenBindingPicker.svelte.d.ts.map +1 -0
- package/dist/canvas/TokenModeSwitcher.svelte +55 -0
- package/dist/canvas/TokenModeSwitcher.svelte.d.ts +18 -0
- package/dist/canvas/TokenModeSwitcher.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasAnimation.svelte.d.ts +98 -0
- package/dist/canvas/createCanvasAnimation.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasAnimation.svelte.js +79 -0
- package/dist/canvas/createCanvasArrange.svelte.d.ts +4 -0
- package/dist/canvas/createCanvasArrange.svelte.d.ts.map +1 -1
- package/dist/canvas/createCanvasAutoLayout.svelte.d.ts.map +1 -1
- package/dist/canvas/createCanvasComponents.svelte.d.ts +68 -0
- package/dist/canvas/createCanvasComponents.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasComponents.svelte.js +31 -0
- package/dist/canvas/createCanvasTokens.svelte.d.ts +31 -0
- package/dist/canvas/createCanvasTokens.svelte.d.ts.map +1 -0
- package/dist/canvas/createCanvasTokens.svelte.js +50 -0
- package/dist/canvas/index.d.ts +12 -0
- package/dist/canvas/index.d.ts.map +1 -1
- package/dist/canvas/index.js +16 -0
- package/dist/docxExport.svelte.d.ts +26 -0
- package/dist/docxExport.svelte.d.ts.map +1 -0
- package/dist/docxExport.svelte.js +36 -0
- package/dist/fileImport.svelte.d.ts +8 -1
- package/dist/fileImport.svelte.d.ts.map +1 -1
- package/dist/fileImport.svelte.js +11 -1
- package/dist/generation/AssetMentionChips.svelte +130 -0
- package/dist/generation/AssetMentionChips.svelte.d.ts +21 -0
- package/dist/generation/AssetMentionChips.svelte.d.ts.map +1 -0
- package/dist/generation/GenerationHistory.svelte +161 -0
- package/dist/generation/GenerationHistory.svelte.d.ts +28 -0
- package/dist/generation/GenerationHistory.svelte.d.ts.map +1 -0
- package/dist/generation/index.d.ts +7 -0
- package/dist/generation/index.d.ts.map +1 -1
- package/dist/generation/index.js +3 -0
- package/dist/generation/records.svelte.d.ts +18 -0
- package/dist/generation/records.svelte.d.ts.map +1 -0
- package/dist/generation/records.svelte.js +66 -0
- package/dist/image-editor/ImageEditor.svelte +72 -1
- package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
- package/dist/image-editor/RefineEdgePanel.svelte +180 -0
- package/dist/image-editor/RefineEdgePanel.svelte.d.ts +13 -0
- package/dist/image-editor/RefineEdgePanel.svelte.d.ts.map +1 -0
- package/dist/image-editor/SelectionOptionsBar.svelte +135 -0
- package/dist/image-editor/SelectionOptionsBar.svelte.d.ts +15 -0
- package/dist/image-editor/SelectionOptionsBar.svelte.d.ts.map +1 -0
- package/dist/image-editor/index.d.ts +6 -0
- package/dist/image-editor/index.d.ts.map +1 -1
- package/dist/image-editor/index.js +2 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/paragraphStyles.svelte.d.ts +30 -0
- package/dist/paragraphStyles.svelte.d.ts.map +1 -0
- package/dist/paragraphStyles.svelte.js +44 -0
- package/dist/review/TrackedChangesList.svelte +67 -0
- package/dist/review/TrackedChangesList.svelte.d.ts +13 -0
- package/dist/review/TrackedChangesList.svelte.d.ts.map +1 -0
- package/dist/review/index.d.ts +3 -0
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +1 -0
- package/dist/review/review.svelte.d.ts +6 -0
- package/dist/review/review.svelte.d.ts.map +1 -1
- package/dist/review/review.svelte.js +3 -0
- package/dist/spreadsheet/FormulaBar.svelte +13 -4
- package/dist/spreadsheet/FormulaBar.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/FormulaHints.svelte +7 -5
- package/dist/spreadsheet/FormulaHints.svelte.d.ts +3 -0
- package/dist/spreadsheet/FormulaHints.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/NameManager.svelte +165 -0
- package/dist/spreadsheet/NameManager.svelte.d.ts +11 -0
- package/dist/spreadsheet/NameManager.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SheetTabs.svelte +161 -0
- package/dist/spreadsheet/SheetTabs.svelte.d.ts +10 -0
- package/dist/spreadsheet/SheetTabs.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte +184 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetConditionalFormatPanel.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte +9 -4
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts +5 -1
- package/dist/spreadsheet/SpreadsheetContextMenu.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte +141 -0
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetDataValidationPanel.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/SpreadsheetEditor.svelte +273 -19
- package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts +8 -0
- package/dist/spreadsheet/SpreadsheetEditor.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/SpreadsheetGroupRow.svelte +31 -3
- package/dist/spreadsheet/SpreadsheetGroupRow.svelte.d.ts +6 -0
- package/dist/spreadsheet/SpreadsheetGroupRow.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte +92 -0
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts +9 -0
- package/dist/spreadsheet/SpreadsheetValidationDialog.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/index.d.ts +10 -0
- package/dist/spreadsheet/index.d.ts.map +1 -1
- package/dist/spreadsheet/index.js +6 -0
- package/dist/spreadsheet/spreadsheet.svelte.d.ts +56 -3
- package/dist/spreadsheet/spreadsheet.svelte.d.ts.map +1 -1
- package/dist/spreadsheet/spreadsheet.svelte.js +105 -6
- package/dist/spreadsheet/viewport.svelte.d.ts +26 -0
- package/dist/spreadsheet/viewport.svelte.d.ts.map +1 -0
- package/dist/spreadsheet/viewport.svelte.js +93 -0
- package/dist/tables/InsertTableGrid.svelte +85 -0
- package/dist/tables/InsertTableGrid.svelte.d.ts +21 -0
- package/dist/tables/InsertTableGrid.svelte.d.ts.map +1 -0
- package/dist/tables/TableBubbleMenu.svelte +43 -0
- package/dist/tables/TableBubbleMenu.svelte.d.ts +18 -0
- package/dist/tables/TableBubbleMenu.svelte.d.ts.map +1 -0
- package/dist/tables/TableToolbar.svelte +81 -0
- package/dist/tables/TableToolbar.svelte.d.ts +19 -0
- package/dist/tables/TableToolbar.svelte.d.ts.map +1 -0
- package/dist/tables/index.d.ts +12 -0
- package/dist/tables/index.d.ts.map +1 -0
- package/dist/tables/index.js +4 -0
- package/dist/tables/tableMenu.svelte.d.ts +21 -0
- package/dist/tables/tableMenu.svelte.d.ts.map +1 -0
- package/dist/tables/tableMenu.svelte.js +27 -0
- package/dist/tokens/TokenSwatch.svelte +14 -0
- package/dist/tokens/TokenSwatch.svelte.d.ts +8 -0
- package/dist/tokens/TokenSwatch.svelte.d.ts.map +1 -0
- package/dist/tokens/TokensPanel.svelte +323 -0
- package/dist/tokens/TokensPanel.svelte.d.ts +14 -0
- package/dist/tokens/TokensPanel.svelte.d.ts.map +1 -0
- package/dist/tokens/createTokens.svelte.d.ts +31 -0
- package/dist/tokens/createTokens.svelte.d.ts.map +1 -0
- package/dist/tokens/createTokens.svelte.js +43 -0
- package/dist/tokens/index.d.ts +7 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +3 -0
- package/package.json +10 -2
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
DATA_VALIDATION_ERROR_STYLE_OPTIONS,
|
|
4
|
+
DATA_VALIDATION_OPERATOR_OPTIONS,
|
|
5
|
+
DATA_VALIDATION_TYPE_OPTIONS,
|
|
6
|
+
describeDataValidation,
|
|
7
|
+
formatRuleRanges,
|
|
8
|
+
parseRuleRanges,
|
|
9
|
+
type DataValidationErrorStyle,
|
|
10
|
+
type DataValidationOperator,
|
|
11
|
+
type DataValidationType
|
|
12
|
+
} from '@pixerate/editor/spreadsheet';
|
|
13
|
+
import type { createReactiveSpreadsheet } from './spreadsheet.svelte.js';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
sheetState: ReturnType<typeof createReactiveSpreadsheet>;
|
|
17
|
+
onClose: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { sheetState, onClose }: Props = $props();
|
|
21
|
+
|
|
22
|
+
const inputClass = 'w-full rounded border border-border bg-background px-1.5 py-0.5 text-xs text-foreground';
|
|
23
|
+
const buttonClass = 'rounded border border-border px-2 py-0.5 hover:bg-muted disabled:opacity-40';
|
|
24
|
+
const selectionText = () => (sheetState.selectedRange ? formatRuleRanges([sheetState.selectedRange]) : 'A1');
|
|
25
|
+
|
|
26
|
+
let rules = $derived(sheetState.document.table?.dataValidations ?? []);
|
|
27
|
+
let type = $state<DataValidationType>('list');
|
|
28
|
+
let operator = $state<DataValidationOperator>('between');
|
|
29
|
+
let formula1 = $state('');
|
|
30
|
+
let formula2 = $state('');
|
|
31
|
+
let errorStyle = $state<DataValidationErrorStyle>('stop');
|
|
32
|
+
let errorMessage = $state('');
|
|
33
|
+
let inputMessage = $state('');
|
|
34
|
+
let ignoreBlank = $state(true);
|
|
35
|
+
let appliesTo = $state(selectionText());
|
|
36
|
+
let ranges = $derived(parseRuleRanges(appliesTo));
|
|
37
|
+
let usesOperator = $derived(type === 'whole' || type === 'decimal' || type === 'date' || type === 'time' || type === 'textLength');
|
|
38
|
+
let needsTwo = $derived(usesOperator && (operator === 'between' || operator === 'notBetween'));
|
|
39
|
+
let circles = $derived.by(() => {
|
|
40
|
+
void sheetState.document;
|
|
41
|
+
return sheetState.getInvalidDataCircles();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function apply() {
|
|
45
|
+
if (!ranges) return;
|
|
46
|
+
sheetState.addDataValidation({
|
|
47
|
+
ranges,
|
|
48
|
+
type,
|
|
49
|
+
...(usesOperator ? { operator } : {}),
|
|
50
|
+
...(formula1 ? { formula1 } : {}),
|
|
51
|
+
...(needsTwo && formula2 ? { formula2 } : {}),
|
|
52
|
+
errorStyle,
|
|
53
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
54
|
+
...(inputMessage ? { inputMessage } : {}),
|
|
55
|
+
ignoreBlank
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<!-- Data validation manager: rules, a form to validate a range, and "circle invalid data". -->
|
|
61
|
+
<div
|
|
62
|
+
role="dialog"
|
|
63
|
+
aria-label="Data validation rules"
|
|
64
|
+
tabindex="-1"
|
|
65
|
+
class="fixed right-4 top-16 z-40 flex max-h-[calc(100vh-5rem)] w-80 flex-col overflow-y-auto rounded-md border border-border bg-popover p-3 text-xs text-popover-foreground shadow-lg"
|
|
66
|
+
style="background: var(--popover, var(--background, #fff));"
|
|
67
|
+
onkeydown={(e) => {
|
|
68
|
+
e.stopPropagation();
|
|
69
|
+
if (e.key === 'Escape') onClose();
|
|
70
|
+
}}
|
|
71
|
+
oncontextmenu={(e) => e.stopPropagation()}
|
|
72
|
+
>
|
|
73
|
+
<div class="mb-2 flex items-center justify-between">
|
|
74
|
+
<h2 class="text-sm font-semibold">Data validation</h2>
|
|
75
|
+
<button type="button" aria-label="Close" class="px-1 text-muted-foreground hover:text-foreground" onclick={onClose}>✕</button>
|
|
76
|
+
</div>
|
|
77
|
+
<ul class="mb-3 flex flex-col gap-1" aria-label="Validation rules">
|
|
78
|
+
{#if rules.length === 0}
|
|
79
|
+
<li class="text-muted-foreground">No validation on this sheet.</li>
|
|
80
|
+
{/if}
|
|
81
|
+
{#each rules as rule (rule.id)}
|
|
82
|
+
<li data-rule-id={rule.id} class="flex items-center gap-1.5 rounded border border-border px-1.5 py-1">
|
|
83
|
+
<div class="min-w-0 flex-1">
|
|
84
|
+
<div class="truncate" title={describeDataValidation(rule)}>{describeDataValidation(rule)}</div>
|
|
85
|
+
<div class="truncate font-mono text-[10px] text-muted-foreground">{formatRuleRanges(rule.ranges)} · {rule.errorStyle ?? 'stop'}</div>
|
|
86
|
+
</div>
|
|
87
|
+
<button type="button" aria-label="Delete rule" class={buttonClass} onclick={() => sheetState.deleteDataValidation(rule.id)}>✕</button>
|
|
88
|
+
</li>
|
|
89
|
+
{/each}
|
|
90
|
+
</ul>
|
|
91
|
+
|
|
92
|
+
<fieldset class="flex flex-col gap-1.5 border-t border-border pt-2">
|
|
93
|
+
<legend class="sr-only">New validation</legend>
|
|
94
|
+
<label class="flex flex-col gap-0.5">
|
|
95
|
+
Allow
|
|
96
|
+
<select aria-label="Allow" class={inputClass} bind:value={type}>
|
|
97
|
+
{#each DATA_VALIDATION_TYPE_OPTIONS as o (o.type)}
|
|
98
|
+
<option value={o.type}>{o.label}</option>
|
|
99
|
+
{/each}
|
|
100
|
+
</select>
|
|
101
|
+
</label>
|
|
102
|
+
{#if usesOperator}
|
|
103
|
+
<select aria-label="Operator" class={inputClass} bind:value={operator}>
|
|
104
|
+
{#each DATA_VALIDATION_OPERATOR_OPTIONS as o (o.operator)}
|
|
105
|
+
<option value={o.operator}>{o.label}</option>
|
|
106
|
+
{/each}
|
|
107
|
+
</select>
|
|
108
|
+
{/if}
|
|
109
|
+
{#if type !== 'any'}
|
|
110
|
+
<input
|
|
111
|
+
aria-label={type === 'list' ? 'Source' : type === 'custom' ? 'Formula' : needsTwo ? 'Minimum' : 'Value'}
|
|
112
|
+
placeholder={type === 'list' ? 'Low,Medium,High or =$H$1:$H$5' : type === 'custom' ? '=A1>0' : 'Value or =formula'}
|
|
113
|
+
class={inputClass}
|
|
114
|
+
bind:value={formula1}
|
|
115
|
+
/>
|
|
116
|
+
{/if}
|
|
117
|
+
{#if needsTwo}
|
|
118
|
+
<input aria-label="Maximum" placeholder="Maximum" class={inputClass} bind:value={formula2} />
|
|
119
|
+
{/if}
|
|
120
|
+
<label class="flex items-center gap-1"><input type="checkbox" bind:checked={ignoreBlank} /> Ignore blank</label>
|
|
121
|
+
<input aria-label="Input message" placeholder="Input message (optional)" class={inputClass} bind:value={inputMessage} />
|
|
122
|
+
<select aria-label="Error style" class={inputClass} bind:value={errorStyle}>
|
|
123
|
+
{#each DATA_VALIDATION_ERROR_STYLE_OPTIONS as o (o.style)}
|
|
124
|
+
<option value={o.style}>{o.label}</option>
|
|
125
|
+
{/each}
|
|
126
|
+
</select>
|
|
127
|
+
<input aria-label="Error message" placeholder="Error message (optional)" class={inputClass} bind:value={errorMessage} />
|
|
128
|
+
<label class="flex flex-col gap-0.5">
|
|
129
|
+
Applies to
|
|
130
|
+
<input aria-label="Applies to" class="{inputClass} font-mono" bind:value={appliesTo} />
|
|
131
|
+
</label>
|
|
132
|
+
<div class="flex flex-wrap justify-between gap-2">
|
|
133
|
+
<button type="button" class={buttonClass} onclick={() => (appliesTo = selectionText())}>Use selection</button>
|
|
134
|
+
<button type="button" class={buttonClass} disabled={!ranges} onclick={() => ranges?.forEach((r) => sheetState.clearDataValidation(r))}>Clear</button>
|
|
135
|
+
<button type="button" class={buttonClass} disabled={!ranges} onclick={apply}>Apply</button>
|
|
136
|
+
</div>
|
|
137
|
+
<button type="button" class={buttonClass} aria-pressed={circles} onclick={() => sheetState.setInvalidDataCircles(!circles)}>
|
|
138
|
+
{circles ? 'Clear validation circles' : 'Circle invalid data'}
|
|
139
|
+
</button>
|
|
140
|
+
</fieldset>
|
|
141
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { createReactiveSpreadsheet } from './spreadsheet.svelte.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
sheetState: ReturnType<typeof createReactiveSpreadsheet>;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
}
|
|
6
|
+
declare const SpreadsheetDataValidationPanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type SpreadsheetDataValidationPanel = ReturnType<typeof SpreadsheetDataValidationPanel>;
|
|
8
|
+
export default SpreadsheetDataValidationPanel;
|
|
9
|
+
//# sourceMappingURL=SpreadsheetDataValidationPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpreadsheetDataValidationPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/spreadsheet/SpreadsheetDataValidationPanel.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAGvE,UAAU,KAAK;IACb,UAAU,EAAE,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC;IACzD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAsHH,QAAA,MAAM,8BAA8B,2CAAwC,CAAC;AAC7E,KAAK,8BAA8B,GAAG,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC;AACxF,eAAe,8BAA8B,CAAC"}
|
|
@@ -14,14 +14,25 @@
|
|
|
14
14
|
type SpreadsheetViewColumn,
|
|
15
15
|
type SpreadsheetViewRow,
|
|
16
16
|
formatCellForDisplay,
|
|
17
|
-
numberFormatColorToCss
|
|
17
|
+
numberFormatColorToCss,
|
|
18
|
+
getConditionalFormatCss,
|
|
19
|
+
getConditionalFormatIcon,
|
|
20
|
+
getDataBarCss,
|
|
21
|
+
conditionalFormatStyleString,
|
|
22
|
+
type SpreadsheetPanelId,
|
|
23
|
+
type SpreadsheetValidationAction
|
|
18
24
|
} from '@pixerate/editor/spreadsheet';
|
|
19
25
|
import FormulaBar from './FormulaBar.svelte';
|
|
26
|
+
import SpreadsheetValidationDialog from './SpreadsheetValidationDialog.svelte';
|
|
27
|
+
import SpreadsheetConditionalFormatPanel from './SpreadsheetConditionalFormatPanel.svelte';
|
|
28
|
+
import SpreadsheetDataValidationPanel from './SpreadsheetDataValidationPanel.svelte';
|
|
20
29
|
import SpreadsheetContextMenu from './SpreadsheetContextMenu.svelte';
|
|
21
30
|
import SpreadsheetGroupRow from './SpreadsheetGroupRow.svelte';
|
|
22
31
|
import FormulaHints from './FormulaHints.svelte';
|
|
32
|
+
import SheetTabs from './SheetTabs.svelte';
|
|
23
33
|
import { createReactiveSpreadsheet } from './spreadsheet.svelte.js';
|
|
24
34
|
import { attachSpreadsheetClipboard } from './tableInteractions.js';
|
|
35
|
+
import { createSpreadsheetViewport } from './viewport.svelte.js';
|
|
25
36
|
|
|
26
37
|
interface Props {
|
|
27
38
|
sheetState?: ReturnType<typeof createReactiveSpreadsheet>;
|
|
@@ -35,6 +46,14 @@
|
|
|
35
46
|
onAddColumnClick?: () => void;
|
|
36
47
|
onColumnHeaderClick?: (col: SpreadsheetColumn) => void;
|
|
37
48
|
onColumnResize?: (colId: string, width: number) => void;
|
|
49
|
+
/** Show the sheet tabs bar under the grid (add, switch, rename, reorder, delete sheets). */
|
|
50
|
+
showSheetTabs?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Render only the rows and columns in (or near) view. `'auto'` (default)
|
|
53
|
+
* virtualises sheets with more than 200 displayed rows or 60 columns;
|
|
54
|
+
* `true` / `false` force it.
|
|
55
|
+
*/
|
|
56
|
+
virtualize?: boolean | 'auto';
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
let {
|
|
@@ -46,9 +65,58 @@
|
|
|
46
65
|
customCellRenderer,
|
|
47
66
|
onAddColumnClick,
|
|
48
67
|
onColumnHeaderClick,
|
|
49
|
-
onColumnResize
|
|
68
|
+
onColumnResize,
|
|
69
|
+
showSheetTabs = false,
|
|
70
|
+
virtualize = 'auto'
|
|
50
71
|
}: Props = $props();
|
|
51
72
|
|
|
73
|
+
// Virtualised rendering: frozen rows/columns, a spacer, the rows and
|
|
74
|
+
// columns in view, another spacer (everything for small sheets).
|
|
75
|
+
type ColumnSlot = { kind: 'col'; key: string; viewCol: SpreadsheetViewColumn } | { kind: 'space'; key: string; width: number };
|
|
76
|
+
type RowSlot = { kind: 'row'; key: string; viewRow: SpreadsheetViewRow } | { kind: 'space'; key: string; height: number };
|
|
77
|
+
let scrollContainer = $state<HTMLDivElement | null>(null);
|
|
78
|
+
const viewportState = createSpreadsheetViewport({
|
|
79
|
+
view: () => sheetState.viewModel,
|
|
80
|
+
virtualize: () => virtualize,
|
|
81
|
+
merges: () => sheetState.document.table?.merges,
|
|
82
|
+
activeCell: () => sheetState.activeCell
|
|
83
|
+
});
|
|
84
|
+
$effect(() => viewportState.attach(scrollContainer));
|
|
85
|
+
let viewWindow = $derived(viewportState.window);
|
|
86
|
+
let columnSlots = $derived.by<ColumnSlot[]>(() => {
|
|
87
|
+
const view = sheetState.viewModel;
|
|
88
|
+
const win = viewWindow;
|
|
89
|
+
const col = (viewCol: SpreadsheetViewColumn): ColumnSlot => ({ kind: 'col', key: viewCol.column.id, viewCol });
|
|
90
|
+
if (!win.virtual) return view.columns.map(col);
|
|
91
|
+
const slots = view.columns.slice(0, win.frozenColumns).map(col);
|
|
92
|
+
if (win.leftSpace > 0) slots.push({ kind: 'space', key: 'space:left', width: win.leftSpace });
|
|
93
|
+
for (let i = win.colStart; i < win.colEnd; i++) slots.push(col(view.columns[i]));
|
|
94
|
+
if (win.rightSpace > 0) slots.push({ kind: 'space', key: 'space:right', width: win.rightSpace });
|
|
95
|
+
return slots;
|
|
96
|
+
});
|
|
97
|
+
let renderedColumns = $derived(columnSlots.flatMap((slot) => (slot.kind === 'col' ? [slot.viewCol] : [])));
|
|
98
|
+
let rowSlots = $derived.by<RowSlot[]>(() => {
|
|
99
|
+
const view = sheetState.viewModel;
|
|
100
|
+
const win = viewWindow;
|
|
101
|
+
const row = (viewRow: SpreadsheetViewRow): RowSlot => ({
|
|
102
|
+
kind: 'row',
|
|
103
|
+
key: viewRow.kind === 'group' ? `group:${viewRow.group.key}` : viewRow.row.id,
|
|
104
|
+
viewRow
|
|
105
|
+
});
|
|
106
|
+
if (!win.virtual) return view.rows.map(row);
|
|
107
|
+
const slots = view.rows.slice(0, win.frozenRows).map(row);
|
|
108
|
+
if (win.topSpace > 0) slots.push({ kind: 'space', key: 'space:top', height: win.topSpace });
|
|
109
|
+
for (let i = win.rowStart; i < win.rowEnd; i++) slots.push(row(view.rows[i]));
|
|
110
|
+
if (win.bottomSpace > 0) slots.push({ kind: 'space', key: 'space:bottom', height: win.bottomSpace });
|
|
111
|
+
return slots;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Defined names for formula autocomplete in the cell editor.
|
|
115
|
+
let nameSuggestions = $derived.by(() => {
|
|
116
|
+
void sheetState.document;
|
|
117
|
+
return sheetState.controller.getNameSuggestions();
|
|
118
|
+
});
|
|
119
|
+
|
|
52
120
|
let isReadOnly = $derived(readOnly || readonly);
|
|
53
121
|
|
|
54
122
|
let gridContainer = $state<HTMLDivElement | null>(null);
|
|
@@ -85,6 +153,73 @@
|
|
|
85
153
|
|
|
86
154
|
let fillDrag = $state.raw<{ source: CellRange; target: CellRange | null } | null>(null);
|
|
87
155
|
let menu = $state.raw<{ x: number; y: number; items: SpreadsheetMenuItem[] } | null>(null);
|
|
156
|
+
// Conditional formatting / data validation manager, opened from the context menu.
|
|
157
|
+
let panel = $state<SpreadsheetPanelId | null>(null);
|
|
158
|
+
// List dropdown of a list-validated cell.
|
|
159
|
+
let listMenu = $state.raw<{ x: number; y: number; row: number; col: number; values: string[] } | null>(null);
|
|
160
|
+
|
|
161
|
+
// --- Data validation: list dropdown, input message, alerts ---
|
|
162
|
+
let activeListValues = $derived.by(() => {
|
|
163
|
+
void sheetState.document;
|
|
164
|
+
const cell = sheetState.activeCell;
|
|
165
|
+
return cell && !isReadOnly ? sheetState.controller.getValidationListValues(cell.row, cell.col) : null;
|
|
166
|
+
});
|
|
167
|
+
let inputMessage = $derived.by(() => {
|
|
168
|
+
void sheetState.document;
|
|
169
|
+
const cell = sheetState.activeCell;
|
|
170
|
+
const rule = cell ? sheetState.controller.getDataValidationAt(cell.row, cell.col) : undefined;
|
|
171
|
+
return rule && rule.showInputMessage !== false && (rule.inputTitle || rule.inputMessage) ? rule : null;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// "Circle invalid data": recomputed when the document (or the toggle) changes.
|
|
175
|
+
let invalidCircles = $derived.by(() => {
|
|
176
|
+
void sheetState.document;
|
|
177
|
+
if (!sheetState.controller.getInvalidDataCircles()) return null;
|
|
178
|
+
return new Set(sheetState.controller.findInvalidCells().map((c) => `${c.row}:${c.col}`));
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
function openListMenu(row: number, col: number, anchor?: HTMLElement | null) {
|
|
182
|
+
if (isReadOnly || sheetState.document.columns[col]?.readOnly) return;
|
|
183
|
+
const values = sheetState.controller.getValidationListValues(row, col);
|
|
184
|
+
if (!values) return;
|
|
185
|
+
const td = anchor ?? gridContainer?.querySelector<HTMLElement>(`td[data-row="${row}"][data-col="${col}"]`);
|
|
186
|
+
const rect = td?.getBoundingClientRect();
|
|
187
|
+
listMenu = { x: rect ? rect.left : 0, y: rect ? rect.bottom : 0, row, col, values };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Close the list on a press outside it (or on its arrow, which toggles it).
|
|
191
|
+
$effect(() => {
|
|
192
|
+
if (!listMenu) return;
|
|
193
|
+
const close = (e: MouseEvent) => {
|
|
194
|
+
if (!(e.target as Element | null)?.closest?.('[aria-label="Allowed values"], .spreadsheet-list-arrow')) listMenu = null;
|
|
195
|
+
};
|
|
196
|
+
window.addEventListener('mousedown', close);
|
|
197
|
+
return () => window.removeEventListener('mousedown', close);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
function chooseListValue(value: string) {
|
|
201
|
+
if (!listMenu) return;
|
|
202
|
+
const row = sheetState.document.rows[listMenu.row];
|
|
203
|
+
const col = sheetState.document.columns[listMenu.col];
|
|
204
|
+
listMenu = null;
|
|
205
|
+
if (row && col) sheetState.commitCellInput(row.id, col.id, value);
|
|
206
|
+
gridContainer?.focus();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function resolveValidation(action: SpreadsheetValidationAction) {
|
|
210
|
+
sheetState.resolveValidationPrompt(action);
|
|
211
|
+
// Back to the cell editor when it is still open (retry), else the grid.
|
|
212
|
+
requestAnimationFrame(() => {
|
|
213
|
+
if (cellInput) cellInput.focus();
|
|
214
|
+
else gridContainer?.focus();
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Inline style of a cell's conditional look (fill, font), keeping the selection tint visible. */
|
|
219
|
+
function conditionalStyle(fill: string | undefined, css: string, selected: boolean): string {
|
|
220
|
+
if (!css) return '';
|
|
221
|
+
return ` ${css}${fill && selected ? ' box-shadow: inset 0 0 0 9999px color-mix(in srgb, var(--primary, #6366f1) 12%, transparent);' : ''}`;
|
|
222
|
+
}
|
|
88
223
|
let filteredColIds = $derived.by(() => {
|
|
89
224
|
const filter = sheetState.document.table?.filter;
|
|
90
225
|
return new Set(filter?.enabled ? filter.rules.map((r) => r.colId) : []);
|
|
@@ -174,11 +309,12 @@
|
|
|
174
309
|
sheetState.cancelEditing();
|
|
175
310
|
} else if (e.key === 'Enter') {
|
|
176
311
|
e.preventDefault();
|
|
177
|
-
|
|
312
|
+
// Invalid input keeps the editor open behind the validation alert.
|
|
313
|
+
if (sheetState.commitEditing().status !== 'committed') return;
|
|
178
314
|
sheetState.moveCursor('down');
|
|
179
315
|
} else if (e.key === 'Tab') {
|
|
180
316
|
e.preventDefault();
|
|
181
|
-
sheetState.commitEditing();
|
|
317
|
+
if (sheetState.commitEditing().status !== 'committed') return;
|
|
182
318
|
sheetState.moveCursor(e.shiftKey ? 'left' : 'right');
|
|
183
319
|
} else {
|
|
184
320
|
return;
|
|
@@ -190,7 +326,11 @@
|
|
|
190
326
|
|
|
191
327
|
if (!sheetState.activeCell) return;
|
|
192
328
|
|
|
193
|
-
if (e.key === '
|
|
329
|
+
if (e.altKey && e.key === 'ArrowDown') {
|
|
330
|
+
// Alt+Down opens the list of a list-validated cell.
|
|
331
|
+
e.preventDefault();
|
|
332
|
+
openListMenu(sheetState.activeCell.row, sheetState.activeCell.col);
|
|
333
|
+
} else if (e.key === 'ArrowUp') {
|
|
194
334
|
e.preventDefault();
|
|
195
335
|
sheetState.moveCursor('up', e.shiftKey);
|
|
196
336
|
} else if (e.key === 'ArrowDown') {
|
|
@@ -443,6 +583,7 @@
|
|
|
443
583
|
}
|
|
444
584
|
</script>
|
|
445
585
|
|
|
586
|
+
{#snippet grid()}
|
|
446
587
|
<div
|
|
447
588
|
bind:this={gridContainer}
|
|
448
589
|
class="flex flex-col h-full w-full overflow-hidden border border-border bg-background select-none outline-none text-foreground font-sans text-xs {className}"
|
|
@@ -455,10 +596,15 @@
|
|
|
455
596
|
{#if showFormulaBar}
|
|
456
597
|
<FormulaBar {sheetState} />
|
|
457
598
|
{/if}
|
|
599
|
+
{#if inputMessage}
|
|
600
|
+
<div role="note" data-validation-input-message="" class="border-b border-border bg-muted/30 px-3 py-1 text-[11px] text-muted-foreground">
|
|
601
|
+
{#if inputMessage.inputTitle}<span class="mr-1 font-semibold text-foreground">{inputMessage.inputTitle}</span>{/if}{inputMessage.inputMessage ?? ''}
|
|
602
|
+
</div>
|
|
603
|
+
{/if}
|
|
458
604
|
|
|
459
605
|
<!-- Main Scrollable Grid -->
|
|
460
|
-
<div class="relative flex-1 overflow-auto">
|
|
461
|
-
<table class="border-
|
|
606
|
+
<div bind:this={scrollContainer} class="relative flex-1 overflow-auto" onscroll={viewportState.onScroll}>
|
|
607
|
+
<table class="border-separate border-spacing-0 table-fixed w-max min-w-full [&>*>tr:not([data-spacer])>*]:border-b [&>*>tr>*]:border-border">
|
|
462
608
|
<!-- Table Header Columns -->
|
|
463
609
|
<thead>
|
|
464
610
|
<tr class="h-7 bg-muted sticky top-0 z-20 border-b border-border shadow-xs">
|
|
@@ -466,7 +612,11 @@
|
|
|
466
612
|
<th class="w-12 min-w-[3rem] sticky left-0 z-30 bg-muted border-r border-border text-center text-[10px] text-muted-foreground font-medium">
|
|
467
613
|
#
|
|
468
614
|
</th>
|
|
469
|
-
{#each
|
|
615
|
+
{#each columnSlots as slot (slot.key)}
|
|
616
|
+
{#if slot.kind === 'space'}
|
|
617
|
+
<th aria-hidden="true" data-spacer="" class="p-0" style="width: {slot.width}px; min-width: {slot.width}px; max-width: {slot.width}px; padding: 0;"></th>
|
|
618
|
+
{:else}
|
|
619
|
+
{@const viewCol = slot.viewCol}
|
|
470
620
|
{@const col = viewCol.column}
|
|
471
621
|
{@const cIdx = viewCol.index}
|
|
472
622
|
<th
|
|
@@ -502,6 +652,7 @@
|
|
|
502
652
|
></div>
|
|
503
653
|
</div>
|
|
504
654
|
</th>
|
|
655
|
+
{/if}
|
|
505
656
|
{/each}
|
|
506
657
|
{#if onAddColumnClick}
|
|
507
658
|
<th class="w-10 min-w-[2.5rem] px-1 text-center bg-muted/40 hover:bg-muted cursor-pointer transition-colors" onclick={onAddColumnClick} title="Add Column">
|
|
@@ -515,11 +666,21 @@
|
|
|
515
666
|
|
|
516
667
|
<!-- Table Body Rows -->
|
|
517
668
|
<tbody>
|
|
518
|
-
{#each
|
|
669
|
+
{#each rowSlots as rowSlot (rowSlot.key)}
|
|
670
|
+
{#if rowSlot.kind === 'space'}
|
|
671
|
+
<tr aria-hidden="true" data-spacer="" style="height: {rowSlot.height}px;">
|
|
672
|
+
<td colspan={columnSlots.length + 3} style="padding: 0; border: 0;"></td>
|
|
673
|
+
</tr>
|
|
674
|
+
{:else}
|
|
675
|
+
{@const viewRow = rowSlot.viewRow}
|
|
519
676
|
{#if viewRow.kind === 'group'}
|
|
520
677
|
<SpreadsheetGroupRow
|
|
521
678
|
group={viewRow.group}
|
|
522
|
-
columns={
|
|
679
|
+
columns={renderedColumns}
|
|
680
|
+
labelColumns={sheetState.viewModel.columns}
|
|
681
|
+
leadingColumns={viewWindow.virtual ? viewWindow.frozenColumns : undefined}
|
|
682
|
+
leftSpace={viewWindow.leftSpace}
|
|
683
|
+
rightSpace={viewWindow.rightSpace}
|
|
523
684
|
groupColId={sheetState.document.table?.grouping?.colId ?? ''}
|
|
524
685
|
onToggle={sheetState.toggleGroupCollapsed}
|
|
525
686
|
trailingCell={!!onAddColumnClick}
|
|
@@ -540,7 +701,11 @@
|
|
|
540
701
|
</td>
|
|
541
702
|
|
|
542
703
|
<!-- Cells (a merged cell renders its top-left cell, spanning the merge) -->
|
|
543
|
-
{#each
|
|
704
|
+
{#each columnSlots as slot (slot.key)}
|
|
705
|
+
{#if slot.kind === 'space'}
|
|
706
|
+
<td aria-hidden="true" data-spacer="" style="padding: 0;"></td>
|
|
707
|
+
{:else}
|
|
708
|
+
{@const viewCol = slot.viewCol}
|
|
544
709
|
{@const span = sheetState.viewModel.getCellSpan(displayRowIndex, viewCol.index)}
|
|
545
710
|
{#if span}
|
|
546
711
|
{@const rIdx = span.source.row}
|
|
@@ -552,6 +717,10 @@
|
|
|
552
717
|
{@const active = isActiveCell(rIdx, cIdx)}
|
|
553
718
|
{@const isEditing = sheetState.editingCell?.row === rIdx && sheetState.editingCell?.col === cIdx}
|
|
554
719
|
|
|
720
|
+
{@const conditional = sheetState.conditionalFormatOverlay.get(rIdx, cIdx)}
|
|
721
|
+
{@const conditionalCss = conditionalFormatStyleString(getConditionalFormatCss(conditional))}
|
|
722
|
+
{@const circled = !!invalidCircles?.has(`${rIdx}:${cIdx}`)}
|
|
723
|
+
{@const showListArrow = active && !isEditing && !!activeListValues}
|
|
555
724
|
{@const inFillPreview = !!fillDrag?.target && !selected && rangeContains(fillDrag.target, displayRowIndex, viewCol.index)}
|
|
556
725
|
{@const showFillHandle =
|
|
557
726
|
!isReadOnly &&
|
|
@@ -568,7 +737,8 @@
|
|
|
568
737
|
rowspan={span.rowSpan}
|
|
569
738
|
colspan={span.colSpan}
|
|
570
739
|
class="relative border-r border-border px-2 py-1 truncate max-w-0 text-foreground transition-colors {selected ? 'bg-primary/10' : ''} {active ? 'ring-2 ring-primary ring-inset z-10' : ''} {viewCol.header ? 'font-semibold' : ''}"
|
|
571
|
-
style="max-width: 0; overflow: hidden; {frozenCellStyle(viewRow, viewCol)}{inFillPreview ? ' outline: 1px dashed var(--primary, #6366f1); outline-offset: -1px;' : ''}"
|
|
740
|
+
style="max-width: 0; overflow: hidden; {frozenCellStyle(viewRow, viewCol)}{conditionalStyle(conditional?.fill, conditionalCss, selected)}{inFillPreview ? ' outline: 1px dashed var(--primary, #6366f1); outline-offset: -1px;' : ''}"
|
|
741
|
+
data-cf-fill={conditional?.fill}
|
|
572
742
|
onclick={(e) => handleCellClick(rIdx, cIdx, e)}
|
|
573
743
|
ondblclick={() => handleCellDblClick(rIdx, cIdx)}
|
|
574
744
|
oncontextmenu={(e) => handleCellContextMenu(rIdx, cIdx, e)}
|
|
@@ -582,20 +752,58 @@
|
|
|
582
752
|
bind:value={sheetState.draftValue}
|
|
583
753
|
onblur={() => sheetState.commitEditing()}
|
|
584
754
|
/>
|
|
585
|
-
<FormulaHints input={cellInput} value={sheetState.draftValue} onAccept={(next) => (sheetState.draftValue = next)} />
|
|
755
|
+
<FormulaHints input={cellInput} value={sheetState.draftValue} onAccept={(next) => (sheetState.draftValue = next)} names={nameSuggestions} />
|
|
586
756
|
{:else if customCellRenderer}
|
|
587
757
|
{@const cellArg = Object.assign({ row, col, cell }, cell)}
|
|
588
758
|
{@render (customCellRenderer as any)(cellArg, col, row)}
|
|
589
759
|
{:else}
|
|
590
760
|
{@const shown = formatCellForDisplay(cell)}
|
|
761
|
+
{@const textColor = conditional?.color ?? (shown.color ? numberFormatColorToCss(shown.color) : undefined)}
|
|
762
|
+
{@const icon = conditional?.icon ? getConditionalFormatIcon(conditional.icon.set, conditional.icon.index) : null}
|
|
763
|
+
{#if conditional?.dataBar}
|
|
764
|
+
<span aria-hidden="true" class="spreadsheet-data-bar" style={conditionalFormatStyleString(getDataBarCss(conditional.dataBar))}></span>
|
|
765
|
+
{/if}
|
|
766
|
+
{#snippet cellText()}
|
|
767
|
+
<span
|
|
768
|
+
class="truncate block max-w-full {cell.error ? 'text-destructive font-semibold' : ''}"
|
|
769
|
+
title={cell.error ? (cell.errorMessage ?? cell.error) : undefined}
|
|
770
|
+
data-numeric={shown.numeric ? '' : undefined}
|
|
771
|
+
style="{conditional?.dataBar || icon ? 'position: relative;' : ''}{icon ? ' flex: 1; min-width: 0;' : ''}{shown.numeric ? ' text-align: right;' : ''}{textColor ? ` color: ${textColor};` : ''}"
|
|
772
|
+
>
|
|
773
|
+
{conditional?.hideValue ? '' : shown.text}
|
|
774
|
+
</span>
|
|
775
|
+
{/snippet}
|
|
776
|
+
{#if icon && conditional?.icon}
|
|
777
|
+
<span class="flex items-center gap-1 min-w-0" style="position: relative;">
|
|
778
|
+
<span class="spreadsheet-cf-icon shrink-0" role="img" aria-label={icon.label} data-icon={conditional.icon.id} style="color: {icon.color};">{icon.glyph}</span>
|
|
779
|
+
{@render cellText()}
|
|
780
|
+
</span>
|
|
781
|
+
{:else}
|
|
782
|
+
{@render cellText()}
|
|
783
|
+
{/if}
|
|
784
|
+
{/if}
|
|
785
|
+
{#if circled}
|
|
591
786
|
<span
|
|
592
|
-
class="
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
787
|
+
class="spreadsheet-invalid-circle"
|
|
788
|
+
aria-label="Invalid value"
|
|
789
|
+
style="position: absolute; inset: 1px 2px; border: 2px solid #e11d48; border-radius: 50%; pointer-events: none; z-index: 6;"
|
|
790
|
+
></span>
|
|
791
|
+
{/if}
|
|
792
|
+
{#if showListArrow}
|
|
793
|
+
<button
|
|
794
|
+
type="button"
|
|
795
|
+
class="spreadsheet-list-arrow"
|
|
796
|
+
aria-label="Show allowed values"
|
|
797
|
+
aria-haspopup="listbox"
|
|
798
|
+
tabindex="-1"
|
|
799
|
+
style="position: absolute; right: 1px; top: 1px; bottom: 1px; width: 16px; z-index: 24; font-size: 10px; line-height: 1; background: var(--muted, #f4f4f5); border: 1px solid var(--border, #e4e4e7); border-radius: 2px; cursor: pointer; padding: 0;"
|
|
800
|
+
onmousedown={(e) => e.stopPropagation()}
|
|
801
|
+
onclick={(e) => {
|
|
802
|
+
e.stopPropagation();
|
|
803
|
+
if (listMenu) listMenu = null;
|
|
804
|
+
else openListMenu(rIdx, cIdx, (e.currentTarget as HTMLElement).parentElement);
|
|
805
|
+
}}>▾</button
|
|
596
806
|
>
|
|
597
|
-
{shown.text}
|
|
598
|
-
</span>
|
|
599
807
|
{/if}
|
|
600
808
|
{#if activeSpill && rangeContains(activeSpill.range, rIdx, cIdx)}
|
|
601
809
|
<span aria-hidden="true" class="spreadsheet-spill-border" style={spillBorderStyle(activeSpill.range, rIdx, cIdx)}></span>
|
|
@@ -612,6 +820,7 @@
|
|
|
612
820
|
{/if}
|
|
613
821
|
</td>
|
|
614
822
|
{/if}
|
|
823
|
+
{/if}
|
|
615
824
|
{/each}
|
|
616
825
|
{#if onAddColumnClick}
|
|
617
826
|
<td class="w-10 min-w-[2.5rem] bg-muted/10 border-r border-border"></td>
|
|
@@ -620,6 +829,7 @@
|
|
|
620
829
|
<td class="border-b border-border bg-transparent p-0"></td>
|
|
621
830
|
</tr>
|
|
622
831
|
{/if}
|
|
832
|
+
{/if}
|
|
623
833
|
{/each}
|
|
624
834
|
</tbody>
|
|
625
835
|
</table>
|
|
@@ -633,9 +843,53 @@
|
|
|
633
843
|
menu = null;
|
|
634
844
|
gridContainer?.focus();
|
|
635
845
|
}}
|
|
846
|
+
onOpenPanel={(next) => (panel = next)}
|
|
636
847
|
/>
|
|
637
848
|
{/if}
|
|
849
|
+
{#if panel === 'conditionalFormatting'}
|
|
850
|
+
<SpreadsheetConditionalFormatPanel {sheetState} onClose={() => (panel = null)} />
|
|
851
|
+
{:else if panel === 'dataValidation'}
|
|
852
|
+
<SpreadsheetDataValidationPanel {sheetState} onClose={() => (panel = null)} />
|
|
853
|
+
{/if}
|
|
854
|
+
{#if listMenu}
|
|
855
|
+
<div
|
|
856
|
+
role="listbox"
|
|
857
|
+
aria-label="Allowed values"
|
|
858
|
+
tabindex="-1"
|
|
859
|
+
class="fixed z-50 max-h-60 min-w-[8rem] overflow-y-auto rounded-md border border-border bg-popover py-1 text-xs text-popover-foreground shadow-lg"
|
|
860
|
+
style="left: {listMenu.x}px; top: {listMenu.y}px; background: var(--popover, var(--background, #fff));"
|
|
861
|
+
onkeydown={(e) => {
|
|
862
|
+
e.stopPropagation();
|
|
863
|
+
if (e.key === 'Escape') {
|
|
864
|
+
listMenu = null;
|
|
865
|
+
gridContainer?.focus();
|
|
866
|
+
}
|
|
867
|
+
}}
|
|
868
|
+
>
|
|
869
|
+
{#if listMenu.values.length === 0}
|
|
870
|
+
<div class="px-3 py-1 text-muted-foreground">No values</div>
|
|
871
|
+
{/if}
|
|
872
|
+
{#each listMenu.values as value, i (`${i}:${value}`)}
|
|
873
|
+
<button type="button" role="option" aria-selected="false" class="block w-full px-3 py-1 text-left hover:bg-muted" onclick={() => chooseListValue(value)}
|
|
874
|
+
>{value}</button
|
|
875
|
+
>
|
|
876
|
+
{/each}
|
|
877
|
+
</div>
|
|
878
|
+
{/if}
|
|
879
|
+
{#if sheetState.validationPrompt}
|
|
880
|
+
<SpreadsheetValidationDialog prompt={sheetState.validationPrompt} onResolve={resolveValidation} />
|
|
881
|
+
{/if}
|
|
638
882
|
</div>
|
|
883
|
+
{/snippet}
|
|
884
|
+
|
|
885
|
+
{#if showSheetTabs}
|
|
886
|
+
<div class="flex h-full w-full flex-col">
|
|
887
|
+
<div class="min-h-0 flex-1">{@render grid()}</div>
|
|
888
|
+
<SheetTabs {sheetState} readOnly={isReadOnly} />
|
|
889
|
+
</div>
|
|
890
|
+
{:else}
|
|
891
|
+
{@render grid()}
|
|
892
|
+
{/if}
|
|
639
893
|
|
|
640
894
|
<style>
|
|
641
895
|
.col-resize-handle {
|
|
@@ -15,6 +15,14 @@ interface Props {
|
|
|
15
15
|
onAddColumnClick?: () => void;
|
|
16
16
|
onColumnHeaderClick?: (col: SpreadsheetColumn) => void;
|
|
17
17
|
onColumnResize?: (colId: string, width: number) => void;
|
|
18
|
+
/** Show the sheet tabs bar under the grid (add, switch, rename, reorder, delete sheets). */
|
|
19
|
+
showSheetTabs?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Render only the rows and columns in (or near) view. `'auto'` (default)
|
|
22
|
+
* virtualises sheets with more than 200 displayed rows or 60 columns;
|
|
23
|
+
* `true` / `false` force it.
|
|
24
|
+
*/
|
|
25
|
+
virtualize?: boolean | 'auto';
|
|
18
26
|
}
|
|
19
27
|
declare const SpreadsheetEditor: import("svelte").Component<Props, {}, "">;
|
|
20
28
|
type SpreadsheetEditor = ReturnType<typeof SpreadsheetEditor>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpreadsheetEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/spreadsheet/SpreadsheetEditor.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAMH,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"SpreadsheetEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/spreadsheet/SpreadsheetEditor.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAMH,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,QAAQ,EAad,MAAM,8BAA8B,CAAC;AASxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAKlE,UAAU,KAAK;IACb,UAAU,CAAC,EAAE,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EACf,OAAO,CAAC,CAAC,QAAQ,EAAE,iBAAiB,EAAE,cAAc,CAAC,CAAC,GACtD,OAAO,CAAC,CAAC;QAAE,GAAG,EAAE,cAAc,CAAC;QAAC,GAAG,EAAE,iBAAiB,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC,CAAC;IAC/E,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACvD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,4FAA4F;IAC5F,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC/B;AAsvBH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|