@veltra/sheet 2.0.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 (137) hide show
  1. package/dist/core/address.d.ts +60 -0
  2. package/dist/core/address.js +142 -0
  3. package/dist/core/address.js.map +1 -0
  4. package/dist/core/cell-store.d.ts +61 -0
  5. package/dist/core/cell-store.js +135 -0
  6. package/dist/core/cell-store.js.map +1 -0
  7. package/dist/core/command/default-registry.d.ts +10 -0
  8. package/dist/core/command/default-registry.js +18 -0
  9. package/dist/core/command/default-registry.js.map +1 -0
  10. package/dist/core/command/history.d.ts +64 -0
  11. package/dist/core/command/history.js +127 -0
  12. package/dist/core/command/history.js.map +1 -0
  13. package/dist/core/command/merge-cells.d.ts +21 -0
  14. package/dist/core/command/merge-cells.js +74 -0
  15. package/dist/core/command/merge-cells.js.map +1 -0
  16. package/dist/core/command/registry.d.ts +17 -0
  17. package/dist/core/command/registry.js +26 -0
  18. package/dist/core/command/registry.js.map +1 -0
  19. package/dist/core/command/set-cell-formula.d.ts +17 -0
  20. package/dist/core/command/set-cell-formula.js +29 -0
  21. package/dist/core/command/set-cell-formula.js.map +1 -0
  22. package/dist/core/command/set-cell-value.d.ts +22 -0
  23. package/dist/core/command/set-cell-value.js +35 -0
  24. package/dist/core/command/set-cell-value.js.map +1 -0
  25. package/dist/core/command/types.d.ts +68 -0
  26. package/dist/core/events.js +29 -0
  27. package/dist/core/events.js.map +1 -0
  28. package/dist/core/formula/ast.d.ts +58 -0
  29. package/dist/core/formula/ast.js +38 -0
  30. package/dist/core/formula/ast.js.map +1 -0
  31. package/dist/core/formula/dependency-graph.d.ts +70 -0
  32. package/dist/core/formula/dependency-graph.js +269 -0
  33. package/dist/core/formula/dependency-graph.js.map +1 -0
  34. package/dist/core/formula/errors.d.ts +25 -0
  35. package/dist/core/formula/errors.js +36 -0
  36. package/dist/core/formula/errors.js.map +1 -0
  37. package/dist/core/formula/evaluator.d.ts +41 -0
  38. package/dist/core/formula/evaluator.js +154 -0
  39. package/dist/core/formula/evaluator.js.map +1 -0
  40. package/dist/core/formula/functions.d.ts +30 -0
  41. package/dist/core/formula/functions.js +209 -0
  42. package/dist/core/formula/functions.js.map +1 -0
  43. package/dist/core/formula/parser.d.ts +7 -0
  44. package/dist/core/formula/parser.js +208 -0
  45. package/dist/core/formula/parser.js.map +1 -0
  46. package/dist/core/formula/tokenizer.d.ts +37 -0
  47. package/dist/core/formula/tokenizer.js +146 -0
  48. package/dist/core/formula/tokenizer.js.map +1 -0
  49. package/dist/core/merge-manager.d.ts +64 -0
  50. package/dist/core/merge-manager.js +111 -0
  51. package/dist/core/merge-manager.js.map +1 -0
  52. package/dist/core/selection.d.ts +31 -0
  53. package/dist/core/selection.js +62 -0
  54. package/dist/core/selection.js.map +1 -0
  55. package/dist/core/sheet.d.ts +105 -0
  56. package/dist/core/sheet.js +202 -0
  57. package/dist/core/sheet.js.map +1 -0
  58. package/dist/core/workbook.d.ts +43 -0
  59. package/dist/core/workbook.js +77 -0
  60. package/dist/core/workbook.js.map +1 -0
  61. package/dist/grid/sheet-grid.d.ts +59 -0
  62. package/dist/grid/sheet-grid.js +230 -0
  63. package/dist/grid/sheet-grid.js.map +1 -0
  64. package/dist/index.d.ts +28 -0
  65. package/dist/index.js +25 -0
  66. package/dist/tools/builtin.js +72 -0
  67. package/dist/tools/builtin.js.map +1 -0
  68. package/dist/tools/context.d.ts +57 -0
  69. package/dist/tools/context.js +43 -0
  70. package/dist/tools/context.js.map +1 -0
  71. package/dist/tools/registry.d.ts +68 -0
  72. package/dist/tools/registry.js +77 -0
  73. package/dist/tools/registry.js.map +1 -0
  74. package/dist/types/index.d.ts +2 -0
  75. package/dist/types/sheet.d.ts +43 -0
  76. package/dist/vue/index.d.ts +2 -0
  77. package/dist/vue/sheet.js +185 -0
  78. package/dist/vue/sheet.js.map +1 -0
  79. package/dist/vue/sheet.vue.d.ts +24 -0
  80. package/dist/vue/style.d.ts +1 -0
  81. package/dist/vue/style.js +1 -0
  82. package/dist/vue/style2.css +93 -0
  83. package/package.json +37 -0
  84. package/src/core/__test__/address.test.ts +113 -0
  85. package/src/core/__test__/cell-store.test.ts +97 -0
  86. package/src/core/__test__/command.test.ts +377 -0
  87. package/src/core/__test__/formula-adversarial.test.ts +265 -0
  88. package/src/core/__test__/formula-engine.test.ts +470 -0
  89. package/src/core/__test__/formula-functions.test.ts +178 -0
  90. package/src/core/__test__/formula-parser.test.ts +160 -0
  91. package/src/core/__test__/history.test.ts +225 -0
  92. package/src/core/__test__/merge-manager.test.ts +106 -0
  93. package/src/core/__test__/sheet.test.ts +142 -0
  94. package/src/core/address.ts +166 -0
  95. package/src/core/cell-store.ts +173 -0
  96. package/src/core/command/default-registry.ts +15 -0
  97. package/src/core/command/history.ts +167 -0
  98. package/src/core/command/index.ts +7 -0
  99. package/src/core/command/merge-cells.ts +74 -0
  100. package/src/core/command/registry.ts +30 -0
  101. package/src/core/command/set-cell-formula.ts +29 -0
  102. package/src/core/command/set-cell-value.ts +38 -0
  103. package/src/core/command/types.ts +73 -0
  104. package/src/core/events.ts +37 -0
  105. package/src/core/formula/ast.ts +66 -0
  106. package/src/core/formula/dependency-graph.ts +329 -0
  107. package/src/core/formula/errors.ts +41 -0
  108. package/src/core/formula/evaluator.ts +219 -0
  109. package/src/core/formula/functions.ts +266 -0
  110. package/src/core/formula/index.ts +7 -0
  111. package/src/core/formula/parser.ts +198 -0
  112. package/src/core/formula/tokenizer.ts +160 -0
  113. package/src/core/merge-manager.ts +158 -0
  114. package/src/core/selection.ts +66 -0
  115. package/src/core/sheet.ts +257 -0
  116. package/src/core/workbook.ts +99 -0
  117. package/src/env.d.ts +7 -0
  118. package/src/grid/__test__/canvas-mock.ts +49 -0
  119. package/src/grid/__test__/setup.ts +3 -0
  120. package/src/grid/__test__/sheet-grid-formula.test.ts +140 -0
  121. package/src/grid/__test__/sheet-grid.test.ts +177 -0
  122. package/src/grid/sheet-grid.ts +283 -0
  123. package/src/index.ts +16 -0
  124. package/src/tools/__test__/builtin.test.ts +128 -0
  125. package/src/tools/__test__/context.test.ts +157 -0
  126. package/src/tools/__test__/registry.test.ts +102 -0
  127. package/src/tools/builtin.ts +75 -0
  128. package/src/tools/context.ts +106 -0
  129. package/src/tools/index.ts +2 -0
  130. package/src/tools/registry.ts +129 -0
  131. package/src/types/index.ts +1 -0
  132. package/src/types/sheet.ts +41 -0
  133. package/src/vue/__test__/sheet-component.test.ts +267 -0
  134. package/src/vue/index.ts +1 -0
  135. package/src/vue/sheet.vue +195 -0
  136. package/src/vue/style.scss +94 -0
  137. package/src/vue/style.ts +1 -0
@@ -0,0 +1,195 @@
1
+ <template>
2
+ <div :class="cls.b">
3
+ <div v-if="showToolbar" :class="cls.e('toolbar')">
4
+ <template v-for="(group, groupIndex) in toolGroups" :key="group.name">
5
+ <span v-if="groupIndex > 0" :class="cls.e('toolbar-divider')" />
6
+ <button
7
+ v-for="item in group.tools"
8
+ :key="item.tool.id"
9
+ type="button"
10
+ :class="cls.e('tool')"
11
+ :disabled="item.disabled"
12
+ :title="item.tool.tooltip ?? item.tool.title"
13
+ @click="handleToolClick(item.tool)"
14
+ >
15
+ <component :is="item.tool.icon" v-if="item.tool.icon" :class="cls.e('tool-icon')" />
16
+ <span>{{ item.tool.title }}</span>
17
+ </button>
18
+ </template>
19
+ </div>
20
+
21
+ <div ref="gridRef" :class="cls.e('grid')" />
22
+
23
+ <div v-if="showTabs" :class="cls.e('tabs')">
24
+ <button
25
+ v-for="(sheet, index) in sheetList"
26
+ :key="sheet.name"
27
+ type="button"
28
+ :class="[cls.e('tab'), bem.is('active', index === activeIndex)]"
29
+ @click="handleTabClick(index)"
30
+ >
31
+ {{ sheet.name }}
32
+ </button>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script lang="ts" setup>
38
+ import { bem } from '@veltra/utils'
39
+ import { computed, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
40
+
41
+ import type { Sheet } from '../core/sheet'
42
+ import { Workbook } from '../core/workbook'
43
+ import { SheetGrid } from '../grid/sheet-grid'
44
+ import { createSheetContext } from '../tools/context'
45
+ import { defaultToolRegistry, type SheetTool } from '../tools/registry'
46
+ import type { SheetEmits, SheetProps, _SheetExposed } from '../types'
47
+
48
+ defineOptions({ name: 'USheet' })
49
+
50
+ const props = withDefaults(defineProps<SheetProps>(), {
51
+ rows: 100,
52
+ cols: 26,
53
+ showToolbar: true,
54
+ showTabs: true
55
+ })
56
+
57
+ const emit = defineEmits<SheetEmits>()
58
+
59
+ const cls = bem('sheet')
60
+
61
+ /** 工作簿:外部传入或内部自建(单 sheet) */
62
+ const internalWorkbook = new Workbook()
63
+ const workbook = computed(() => props.workbook ?? internalWorkbook)
64
+
65
+ /** 激活 sheet 索引(镜像 workbook.activeSheetIndex,经事件同步) */
66
+ const activeIndex = ref(workbook.value.activeSheetIndex)
67
+ /** sheet 列表(tabs 渲染;由 sheets-change 同步) */
68
+ const sheetList = shallowRef<Sheet[]>(workbook.value.getSheets())
69
+ const activeSheet = computed(() => sheetList.value[activeIndex.value] ?? workbook.value.activeSheet)
70
+
71
+ /** 工具上下文:动态解析活动 sheet,tab 切换后自动指向当前 sheet */
72
+ const context = createSheetContext(() => activeSheet.value)
73
+
74
+ const gridRef = useTemplateRef<HTMLElement>('gridRef')
75
+ let grid: SheetGrid | undefined
76
+
77
+ // ─── 工具栏状态刷新 ─────────────────────────────────────────
78
+ // 工具 visible/disabled 是 (ctx) => boolean 纯函数,状态源变化时 bump 触发重算
79
+
80
+ const stateTick = ref(0)
81
+ const bump = (): void => {
82
+ stateTick.value++
83
+ }
84
+
85
+ let disposeSheetEvents: (() => void)[] = []
86
+ /** 订阅活动 sheet 的状态源(tab 切换 / 工作簿变更时重绑) */
87
+ function bindSheetEvents(sheet: Sheet): void {
88
+ for (const dispose of disposeSheetEvents) dispose()
89
+ disposeSheetEvents = [
90
+ sheet.on('selection-change', bump),
91
+ sheet.on('history-change', bump),
92
+ sheet.on('cell-change', bump),
93
+ sheet.on('merge-change', bump)
94
+ ]
95
+ }
96
+
97
+ let disposeWorkbookEvents: (() => void)[] = []
98
+ function bindWorkbookEvents(wb: Workbook): void {
99
+ for (const dispose of disposeWorkbookEvents) dispose()
100
+ disposeWorkbookEvents = [
101
+ wb.on('sheets-change', ({ sheets }) => {
102
+ sheetList.value = sheets
103
+ bump()
104
+ }),
105
+ wb.on('active-sheet-change', ({ sheet, index }) => {
106
+ activeIndex.value = index
107
+ bindSheetEvents(sheet)
108
+ rebuildGrid()
109
+ bump()
110
+ emit('active-sheet-change', { sheet, index })
111
+ })
112
+ ]
113
+ }
114
+
115
+ const offRegistryChange = defaultToolRegistry.onChange(bump)
116
+
117
+ // ─── 工具栏 ────────────────────────────────────────────────
118
+
119
+ const toolGroups = computed(() => {
120
+ void stateTick.value // 依赖状态源:选区 / 历史 / 单元格 / 合并 / 注册表
121
+ return defaultToolRegistry
122
+ .getGroups()
123
+ .map((group) => ({
124
+ name: group.name,
125
+ tools: group.tools
126
+ .map((tool) => ({
127
+ tool,
128
+ visible: tool.visible?.(context) ?? true,
129
+ disabled: tool.disabled?.(context) ?? false
130
+ }))
131
+ .filter((item) => item.visible)
132
+ }))
133
+ .filter((group) => group.tools.length > 0)
134
+ })
135
+
136
+ function handleToolClick(tool: SheetTool): void {
137
+ if (tool.disabled?.(context)) return
138
+ tool.onClick(context)
139
+ }
140
+
141
+ // ─── sheet tabs ─────────────────────────────────────────────
142
+
143
+ function handleTabClick(index: number): void {
144
+ if (index === activeIndex.value) return
145
+ const sheet = sheetList.value[index]
146
+ if (sheet) workbook.value.activateSheet(sheet.name)
147
+ }
148
+
149
+ // ─── grid 生命周期 ──────────────────────────────────────────
150
+
151
+ function rebuildGrid(): void {
152
+ const container = gridRef.value
153
+ if (!container) return
154
+ grid?.release()
155
+ grid = new SheetGrid({ container, sheet: activeSheet.value, rows: props.rows, cols: props.cols })
156
+ }
157
+
158
+ onMounted(() => {
159
+ bindWorkbookEvents(workbook.value)
160
+ bindSheetEvents(activeSheet.value)
161
+ rebuildGrid()
162
+ })
163
+
164
+ watch(() => [props.rows, props.cols], rebuildGrid)
165
+
166
+ watch(
167
+ () => props.workbook,
168
+ (wb, prev) => {
169
+ if (wb === prev) return
170
+ activeIndex.value = workbook.value.activeSheetIndex
171
+ sheetList.value = workbook.value.getSheets()
172
+ bindWorkbookEvents(workbook.value)
173
+ bindSheetEvents(activeSheet.value)
174
+ rebuildGrid()
175
+ bump()
176
+ }
177
+ )
178
+
179
+ onBeforeUnmount(() => {
180
+ offRegistryChange()
181
+ for (const dispose of disposeWorkbookEvents) dispose()
182
+ for (const dispose of disposeSheetEvents) dispose()
183
+ grid?.release()
184
+ grid = undefined
185
+ })
186
+
187
+ const exposed: _SheetExposed = {
188
+ workbook,
189
+ getActiveSheet: () => activeSheet.value,
190
+ getContext: () => context,
191
+ getGrid: () => grid
192
+ }
193
+
194
+ defineExpose(exposed)
195
+ </script>
@@ -0,0 +1,94 @@
1
+ @use 'pkg:@veltra/styles/mixins' as m;
2
+ @use 'pkg:@veltra/styles/functions' as fn;
3
+
4
+ @include m.b(sheet) {
5
+ display: flex;
6
+ flex-direction: column;
7
+ overflow: hidden;
8
+ border: fn.use-var(border);
9
+ border-radius: fn.use-var(radius, default);
10
+ background-color: fn.use-var(bg-color, top);
11
+
12
+ @include m.e(toolbar) {
13
+ display: flex;
14
+ align-items: center;
15
+ flex-wrap: wrap;
16
+ gap: fn.use-var(gap, small);
17
+ padding: fn.use-var(gap, small);
18
+ border-bottom: 1px solid fn.use-var(border, muted-color);
19
+ }
20
+
21
+ @include m.e(toolbar-divider) {
22
+ width: 1px;
23
+ align-self: stretch;
24
+ margin: 2px 0;
25
+ background-color: fn.use-var(border, muted-color);
26
+ }
27
+
28
+ @include m.e(tool) {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 4px;
32
+ height: fn.use-var(form-component-height, small);
33
+ padding: 0 8px;
34
+ border: none;
35
+ border-radius: fn.use-var(radius, small);
36
+ background: transparent;
37
+ color: fn.use-var(text-color, main);
38
+ font-size: fn.use-var(font-size-main, small);
39
+ white-space: nowrap;
40
+ cursor: pointer;
41
+ transition:
42
+ background-color fn.use-var(transition, fast) fn.use-var(transition, ease),
43
+ color fn.use-var(transition, fast) fn.use-var(transition, ease);
44
+
45
+ &:hover:not(:disabled) {
46
+ background-color: fn.use-var(bg-color, hover);
47
+ }
48
+
49
+ &:disabled {
50
+ color: fn.use-var(text-color, disabled);
51
+ cursor: not-allowed;
52
+ }
53
+ }
54
+
55
+ @include m.e(tool-icon) {
56
+ font-size: calc(fn.use-var(font-size-main, small) + 2px);
57
+ }
58
+
59
+ @include m.e(grid) {
60
+ flex: 1;
61
+ min-height: 0;
62
+ }
63
+
64
+ @include m.e(tabs) {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 2px;
68
+ padding: 2px fn.use-var(gap, small);
69
+ border-top: 1px solid fn.use-var(border, muted-color);
70
+ background-color: fn.use-var(bg-color, middle);
71
+ }
72
+
73
+ @include m.e(tab) {
74
+ padding: 4px 12px;
75
+ border: none;
76
+ border-radius: fn.use-var(radius, small);
77
+ background: transparent;
78
+ color: fn.use-var(text-color, second);
79
+ font-size: fn.use-var(font-size-main, small);
80
+ cursor: pointer;
81
+ transition:
82
+ background-color fn.use-var(transition, fast) fn.use-var(transition, ease),
83
+ color fn.use-var(transition, fast) fn.use-var(transition, ease);
84
+
85
+ &:hover {
86
+ background-color: fn.use-var(bg-color, hover);
87
+ }
88
+
89
+ @include m.is(active) {
90
+ background-color: fn.use-var(bg-color, top);
91
+ color: fn.use-var(color, primary);
92
+ }
93
+ }
94
+ }
@@ -0,0 +1 @@
1
+ import './style.scss'