@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/cell-types.js +20 -0
  4. package/dist/cell-types.js.map +1 -0
  5. package/dist/code/code-formula-bar.js +6 -0
  6. package/dist/code/code-formula-bar.js.map +1 -0
  7. package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js +192 -0
  8. package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  9. package/dist/code/index.js +12 -0
  10. package/dist/code/index.js.map +1 -0
  11. package/dist/code/language.js +51 -0
  12. package/dist/code/language.js.map +1 -0
  13. package/dist/code/style.d.ts +1 -0
  14. package/dist/code/style.js +2 -0
  15. package/dist/collaboration/anchor.js +143 -0
  16. package/dist/collaboration/anchor.js.map +1 -0
  17. package/dist/collaboration/index.js +770 -0
  18. package/dist/collaboration/index.js.map +1 -0
  19. package/dist/collaboration/peers.js +87 -0
  20. package/dist/collaboration/peers.js.map +1 -0
  21. package/dist/collaboration/shared.js +450 -0
  22. package/dist/collaboration/shared.js.map +1 -0
  23. package/dist/components/cell-panel.js +6 -0
  24. package/dist/components/cell-panel.js.map +1 -0
  25. package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js +70 -0
  26. package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  27. package/dist/components/cell-renders.js +40 -0
  28. package/dist/components/cell-renders.js.map +1 -0
  29. package/dist/components/date-panel.js +6 -0
  30. package/dist/components/date-panel.js.map +1 -0
  31. package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js +48 -0
  32. package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/components/filter-panel.js +6 -0
  34. package/dist/components/filter-panel.js.map +1 -0
  35. package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js +259 -0
  36. package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/components/format-dialog.js +6 -0
  38. package/dist/components/format-dialog.js.map +1 -0
  39. package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js +432 -0
  40. package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/components/format-list.js +6 -0
  42. package/dist/components/format-list.js.map +1 -0
  43. package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js +187 -0
  44. package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/components/format-presets.js +97 -0
  46. package/dist/components/format-presets.js.map +1 -0
  47. package/dist/components/option-panel.js +6 -0
  48. package/dist/components/option-panel.js.map +1 -0
  49. package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js +117 -0
  50. package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/components/sheet-comment.js +6 -0
  52. package/dist/components/sheet-comment.js.map +1 -0
  53. package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js +104 -0
  54. package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/sheet-formula-bar.js +6 -0
  56. package/dist/components/sheet-formula-bar.js.map +1 -0
  57. package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js +124 -0
  58. package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/components/sheet-link.js +6 -0
  60. package/dist/components/sheet-link.js.map +1 -0
  61. package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js +249 -0
  62. package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/components/sheet-menu.js +6 -0
  64. package/dist/components/sheet-menu.js.map +1 -0
  65. package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js +115 -0
  66. package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  67. package/dist/components/sheet-name-box.js +6 -0
  68. package/dist/components/sheet-name-box.js.map +1 -0
  69. package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js +91 -0
  70. package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js.map +1 -0
  71. package/dist/components/sheet-note.js +6 -0
  72. package/dist/components/sheet-note.js.map +1 -0
  73. package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js +146 -0
  74. package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/components/sheet-outline.js +6 -0
  76. package/dist/components/sheet-outline.js.map +1 -0
  77. package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js +237 -0
  78. package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  79. package/dist/components/sheet-tabs.js +6 -0
  80. package/dist/components/sheet-tabs.js.map +1 -0
  81. package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js +487 -0
  82. package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js.map +1 -0
  83. package/dist/components/sheet-toolbar.js +6 -0
  84. package/dist/components/sheet-toolbar.js.map +1 -0
  85. package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js +1562 -0
  86. package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  87. package/dist/components/validation-dialog.js +6 -0
  88. package/dist/components/validation-dialog.js.map +1 -0
  89. package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js +370 -0
  90. package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  91. package/dist/context.js +12 -0
  92. package/dist/context.js.map +1 -0
  93. package/dist/core/address.js +256 -0
  94. package/dist/core/address.js.map +1 -0
  95. package/dist/core/axis.js +72 -0
  96. package/dist/core/axis.js.map +1 -0
  97. package/dist/core/cell-type.js +30 -0
  98. package/dist/core/cell-type.js.map +1 -0
  99. package/dist/core/clipboard.js +141 -0
  100. package/dist/core/clipboard.js.map +1 -0
  101. package/dist/core/commands.js +63 -0
  102. package/dist/core/commands.js.map +1 -0
  103. package/dist/core/comments.js +74 -0
  104. package/dist/core/comments.js.map +1 -0
  105. package/dist/core/conditional.js +362 -0
  106. package/dist/core/conditional.js.map +1 -0
  107. package/dist/core/csv.js +191 -0
  108. package/dist/core/csv.js.map +1 -0
  109. package/dist/core/data.js +118 -0
  110. package/dist/core/data.js.map +1 -0
  111. package/dist/core/document.js +187 -0
  112. package/dist/core/document.js.map +1 -0
  113. package/dist/core/extension.js +60 -0
  114. package/dist/core/extension.js.map +1 -0
  115. package/dist/core/file.js +336 -0
  116. package/dist/core/file.js.map +1 -0
  117. package/dist/core/fill.js +153 -0
  118. package/dist/core/fill.js.map +1 -0
  119. package/dist/core/find.js +42 -0
  120. package/dist/core/find.js.map +1 -0
  121. package/dist/core/format.js +404 -0
  122. package/dist/core/format.js.map +1 -0
  123. package/dist/core/group.js +134 -0
  124. package/dist/core/group.js.map +1 -0
  125. package/dist/core/history.js +68 -0
  126. package/dist/core/history.js.map +1 -0
  127. package/dist/core/index.js +17 -0
  128. package/dist/core/link.js +34 -0
  129. package/dist/core/link.js.map +1 -0
  130. package/dist/core/merge.js +105 -0
  131. package/dist/core/merge.js.map +1 -0
  132. package/dist/core/model.js +48 -0
  133. package/dist/core/model.js.map +1 -0
  134. package/dist/core/nav.js +44 -0
  135. package/dist/core/nav.js.map +1 -0
  136. package/dist/core/paint.js +106 -0
  137. package/dist/core/paint.js.map +1 -0
  138. package/dist/core/plain-cell-types.js +54 -0
  139. package/dist/core/plain-cell-types.js.map +1 -0
  140. package/dist/core/protect.js +246 -0
  141. package/dist/core/protect.js.map +1 -0
  142. package/dist/core/selection.js +37 -0
  143. package/dist/core/selection.js.map +1 -0
  144. package/dist/core/transaction.js +1582 -0
  145. package/dist/core/transaction.js.map +1 -0
  146. package/dist/core/validation.js +135 -0
  147. package/dist/core/validation.js.map +1 -0
  148. package/dist/core/versions.js +161 -0
  149. package/dist/core/versions.js.map +1 -0
  150. package/dist/export/color.js +36 -0
  151. package/dist/export/color.js.map +1 -0
  152. package/dist/export/draw.js +316 -0
  153. package/dist/export/draw.js.map +1 -0
  154. package/dist/export/index.js +390 -0
  155. package/dist/export/index.js.map +1 -0
  156. package/dist/formula/edit.js +210 -0
  157. package/dist/formula/edit.js.map +1 -0
  158. package/dist/formula/engine.js +526 -0
  159. package/dist/formula/engine.js.map +1 -0
  160. package/dist/formula/error.js +39 -0
  161. package/dist/formula/error.js.map +1 -0
  162. package/dist/formula/functions.js +1493 -0
  163. package/dist/formula/functions.js.map +1 -0
  164. package/dist/formula/help.js +408 -0
  165. package/dist/formula/help.js.map +1 -0
  166. package/dist/formula/index.js +7 -0
  167. package/dist/formula/parse.js +369 -0
  168. package/dist/formula/parse.js.map +1 -0
  169. package/dist/formula/values.js +123 -0
  170. package/dist/formula/values.js.map +1 -0
  171. package/dist/formulas.js +30 -0
  172. package/dist/formulas.js.map +1 -0
  173. package/dist/index.js +39 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/locale.js +1312 -0
  176. package/dist/locale.js.map +1 -0
  177. package/dist/sheet.js +6 -0
  178. package/dist/sheet.js.map +1 -0
  179. package/dist/sheet.vue_vue_type_script_setup_true_lang.js +7166 -0
  180. package/dist/sheet.vue_vue_type_script_setup_true_lang.js.map +1 -0
  181. package/dist/style.css +1 -0
  182. package/dist/style.d.ts +1 -0
  183. package/dist/style.js +17 -0
  184. package/dist/types/cell-types.d.ts +2 -0
  185. package/dist/types/code/code-formula-bar.vue.d.ts +3 -0
  186. package/dist/types/code/index.d.ts +12 -0
  187. package/dist/types/code/language.d.ts +13 -0
  188. package/dist/types/collaboration/anchor.d.ts +65 -0
  189. package/dist/types/collaboration/index.d.ts +59 -0
  190. package/dist/types/collaboration/peers.d.ts +21 -0
  191. package/dist/types/collaboration/shared.d.ts +75 -0
  192. package/dist/types/components/cell-panel.vue.d.ts +20 -0
  193. package/dist/types/components/cell-renders.d.ts +9 -0
  194. package/dist/types/components/date-panel.vue.d.ts +9 -0
  195. package/dist/types/components/filter-panel.vue.d.ts +21 -0
  196. package/dist/types/components/format-dialog.vue.d.ts +24 -0
  197. package/dist/types/components/format-list.vue.d.ts +25 -0
  198. package/dist/types/components/format-presets.d.ts +30 -0
  199. package/dist/types/components/option-panel.vue.d.ts +13 -0
  200. package/dist/types/components/sheet-comment.vue.d.ts +29 -0
  201. package/dist/types/components/sheet-formula-bar.vue.d.ts +3 -0
  202. package/dist/types/components/sheet-link.vue.d.ts +32 -0
  203. package/dist/types/components/sheet-menu.vue.d.ts +20 -0
  204. package/dist/types/components/sheet-name-box.vue.d.ts +3 -0
  205. package/dist/types/components/sheet-note.vue.d.ts +22 -0
  206. package/dist/types/components/sheet-outline.vue.d.ts +36 -0
  207. package/dist/types/components/sheet-tabs.vue.d.ts +9 -0
  208. package/dist/types/components/sheet-toolbar.vue.d.ts +11 -0
  209. package/dist/types/components/validation-dialog.vue.d.ts +25 -0
  210. package/dist/types/context.d.ts +189 -0
  211. package/dist/types/core/address.d.ts +109 -0
  212. package/dist/types/core/axis.d.ts +23 -0
  213. package/dist/types/core/cell-type.d.ts +157 -0
  214. package/dist/types/core/clipboard.d.ts +48 -0
  215. package/dist/types/core/commands.d.ts +76 -0
  216. package/dist/types/core/comments.d.ts +22 -0
  217. package/dist/types/core/conditional.d.ts +114 -0
  218. package/dist/types/core/csv.d.ts +93 -0
  219. package/dist/types/core/data.d.ts +26 -0
  220. package/dist/types/core/document.d.ts +49 -0
  221. package/dist/types/core/extension.d.ts +177 -0
  222. package/dist/types/core/file.d.ts +301 -0
  223. package/dist/types/core/fill.d.ts +23 -0
  224. package/dist/types/core/find.d.ts +39 -0
  225. package/dist/types/core/format.d.ts +47 -0
  226. package/dist/types/core/group.d.ts +76 -0
  227. package/dist/types/core/history.d.ts +27 -0
  228. package/dist/types/core/index.d.ts +33 -0
  229. package/dist/types/core/link.d.ts +14 -0
  230. package/dist/types/core/merge.d.ts +26 -0
  231. package/dist/types/core/model.d.ts +363 -0
  232. package/dist/types/core/nav.d.ts +15 -0
  233. package/dist/types/core/paint.d.ts +19 -0
  234. package/dist/types/core/plain-cell-types.d.ts +6 -0
  235. package/dist/types/core/protect.d.ts +34 -0
  236. package/dist/types/core/selection.d.ts +25 -0
  237. package/dist/types/core/transaction.d.ts +470 -0
  238. package/dist/types/core/validation.d.ts +55 -0
  239. package/dist/types/core/versions.d.ts +38 -0
  240. package/dist/types/export/color.d.ts +7 -0
  241. package/dist/types/export/draw.d.ts +98 -0
  242. package/dist/types/export/index.d.ts +47 -0
  243. package/dist/types/formula/edit.d.ts +62 -0
  244. package/dist/types/formula/engine.d.ts +99 -0
  245. package/dist/types/formula/error.d.ts +30 -0
  246. package/dist/types/formula/functions.d.ts +62 -0
  247. package/dist/types/formula/help.d.ts +12 -0
  248. package/dist/types/formula/index.d.ts +13 -0
  249. package/dist/types/formula/parse.d.ts +92 -0
  250. package/dist/types/formula/values.d.ts +65 -0
  251. package/dist/types/formulas.d.ts +32 -0
  252. package/dist/types/index.d.ts +225 -0
  253. package/dist/types/locale.d.ts +531 -0
  254. package/dist/types/sheet.vue.d.ts +60 -0
  255. package/dist/types/types.d.ts +498 -0
  256. package/dist/types/use-comments.d.ts +47 -0
  257. package/dist/types/use-messages.d.ts +7 -0
  258. package/dist/use-comments.js +130 -0
  259. package/dist/use-comments.js.map +1 -0
  260. package/dist/use-messages.js +29 -0
  261. package/dist/use-messages.js.map +1 -0
  262. package/package.json +93 -4
@@ -0,0 +1,68 @@
1
+ import { applySheetSteps, compactSheetSteps } from "./transaction.js";
2
+ // src/core/history.ts
3
+ /** Transactions in one group merge when they come within this many ms. */
4
+ var GROUP_WINDOW = 1e3;
5
+ var SheetHistory = class {
6
+ depth;
7
+ done = [];
8
+ undone = [];
9
+ constructor(depth = 100) {
10
+ this.depth = depth;
11
+ }
12
+ get canUndo() {
13
+ return this.done.length > 0;
14
+ }
15
+ get canRedo() {
16
+ return this.undone.length > 0;
17
+ }
18
+ record(tr, before, after, now = Date.now()) {
19
+ if (!tr.changed || tr.meta.history === false) return;
20
+ this.undone = [];
21
+ const last = this.done[this.done.length - 1];
22
+ const group = tr.meta.group;
23
+ if (last && group && last.group === group && now - last.time < GROUP_WINDOW) {
24
+ last.steps = compactSheetSteps([...last.steps, ...tr.steps]);
25
+ last.inverse = compactSheetSteps([...tr.inverse, ...last.inverse]);
26
+ last.time = now;
27
+ last.after = after;
28
+ return;
29
+ }
30
+ this.done.push({
31
+ steps: tr.steps.slice(),
32
+ inverse: tr.inverse.slice(),
33
+ group,
34
+ time: now,
35
+ before,
36
+ after
37
+ });
38
+ if (this.done.length > this.depth) this.done.shift();
39
+ }
40
+ undo(doc) {
41
+ const entry = this.done.pop();
42
+ if (!entry) return null;
43
+ this.undone.push(entry);
44
+ return {
45
+ tr: applySheetSteps(doc, entry.inverse),
46
+ view: entry.before
47
+ };
48
+ }
49
+ redo(doc) {
50
+ const entry = this.undone.pop();
51
+ if (!entry) return null;
52
+ this.done.push({
53
+ ...entry,
54
+ group: void 0
55
+ });
56
+ return {
57
+ tr: applySheetSteps(doc, entry.steps),
58
+ view: entry.after
59
+ };
60
+ }
61
+ clear() {
62
+ this.done = [];
63
+ this.undone = [];
64
+ }
65
+ };
66
+ export { SheetHistory };
67
+
68
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","names":[],"sources":["../../src/core/history.ts"],"sourcesContent":["import {\n applySheetSteps,\n compactSheetSteps,\n type SheetStep,\n type SheetTransaction,\n} from './transaction'\nimport type { SheetWorkbook } from './model'\nimport type { SheetSelection } from './selection'\n\n/** What an undo or redo puts back: the sheet and the selection that went with the change. */\nexport interface SheetHistoryView {\n /** The sheet's key (`id`, or its name), so it is found again after sheets move. */\n sheet: string\n selection: SheetSelection\n}\n\ninterface Entry {\n steps: SheetStep[]\n inverse: SheetStep[]\n group?: string\n time: number\n before: SheetHistoryView\n after: SheetHistoryView\n}\n\n/** Transactions in one group merge when they come within this many ms. */\nconst GROUP_WINDOW = 1000\n\nexport class SheetHistory {\n private done: Entry[] = []\n private undone: Entry[] = []\n\n constructor(private depth = 100) {}\n\n get canUndo() {\n return this.done.length > 0\n }\n\n get canRedo() {\n return this.undone.length > 0\n }\n\n record(\n tr: SheetTransaction,\n before: SheetHistoryView,\n after: SheetHistoryView,\n now = Date.now(),\n ) {\n if (!tr.changed || tr.meta.history === false) return\n this.undone = []\n const last = this.done[this.done.length - 1]\n const group = tr.meta.group\n if (last && group && last.group === group && now - last.time < GROUP_WINDOW) {\n last.steps = compactSheetSteps([...last.steps, ...tr.steps])\n last.inverse = compactSheetSteps([...tr.inverse, ...last.inverse])\n last.time = now\n last.after = after\n return\n }\n this.done.push({\n steps: tr.steps.slice(),\n inverse: tr.inverse.slice(),\n group,\n time: now,\n before,\n after,\n })\n if (this.done.length > this.depth) this.done.shift()\n }\n\n undo(doc: SheetWorkbook) {\n const entry = this.done.pop()\n if (!entry) return null\n this.undone.push(entry)\n return { tr: applySheetSteps(doc, entry.inverse), view: entry.before }\n }\n\n redo(doc: SheetWorkbook) {\n const entry = this.undone.pop()\n if (!entry) return null\n this.done.push({ ...entry, group: undefined })\n return { tr: applySheetSteps(doc, entry.steps), view: entry.after }\n }\n\n clear() {\n this.done = []\n this.undone = []\n }\n}\n"],"mappings":";;;AA0BA,IAAM,eAAe;AAErB,IAAa,eAAb,MAA0B;CAIJ;CAHpB,OAAwB,CAAC;CACzB,SAA0B,CAAC;CAE3B,YAAY,QAAgB,KAAK;EAAb,KAAA,QAAA;CAAc;CAElC,IAAI,UAAU;EACZ,OAAO,KAAK,KAAK,SAAS;CAC5B;CAEA,IAAI,UAAU;EACZ,OAAO,KAAK,OAAO,SAAS;CAC9B;CAEA,OACE,IACA,QACA,OACA,MAAM,KAAK,IAAI,GACf;EACA,IAAI,CAAC,GAAG,WAAW,GAAG,KAAK,YAAY,OAAO;EAC9C,KAAK,SAAS,CAAC;EACf,MAAM,OAAO,KAAK,KAAK,KAAK,KAAK,SAAS;EAC1C,MAAM,QAAQ,GAAG,KAAK;EACtB,IAAI,QAAQ,SAAS,KAAK,UAAU,SAAS,MAAM,KAAK,OAAO,cAAc;GAC3E,KAAK,QAAQ,kBAAkB,CAAC,GAAG,KAAK,OAAO,GAAG,GAAG,KAAK,CAAC;GAC3D,KAAK,UAAU,kBAAkB,CAAC,GAAG,GAAG,SAAS,GAAG,KAAK,OAAO,CAAC;GACjE,KAAK,OAAO;GACZ,KAAK,QAAQ;GACb;EACF;EACA,KAAK,KAAK,KAAK;GACb,OAAO,GAAG,MAAM,MAAM;GACtB,SAAS,GAAG,QAAQ,MAAM;GAC1B;GACA,MAAM;GACN;GACA;EACF,CAAC;EACD,IAAI,KAAK,KAAK,SAAS,KAAK,OAAO,KAAK,KAAK,MAAM;CACrD;CAEA,KAAK,KAAoB;EACvB,MAAM,QAAQ,KAAK,KAAK,IAAI;EAC5B,IAAI,CAAC,OAAO,OAAO;EACnB,KAAK,OAAO,KAAK,KAAK;EACtB,OAAO;GAAE,IAAI,gBAAgB,KAAK,MAAM,OAAO;GAAG,MAAM,MAAM;EAAO;CACvE;CAEA,KAAK,KAAoB;EACvB,MAAM,QAAQ,KAAK,OAAO,IAAI;EAC9B,IAAI,CAAC,OAAO,OAAO;EACnB,KAAK,KAAK,KAAK;GAAE,GAAG;GAAO,OAAO,KAAA;EAAU,CAAC;EAC7C,OAAO;GAAE,IAAI,gBAAgB,KAAK,MAAM,KAAK;GAAG,MAAM,MAAM;EAAM;CACpE;CAEA,QAAQ;EACN,KAAK,OAAO,CAAC;EACb,KAAK,SAAS,CAAC;CACjB;AACF"}
@@ -0,0 +1,17 @@
1
+ import { boundsOf, columnIndex, columnName, dropReferences, formatCell, formatRange, moveReferences, parseCell, parseRange, quoteSheet, renameReferences, renameReferencesAll, shiftFormula, splitSheet } from "./address.js";
2
+ import { changeDecimals, formatValue, parseInput } from "./format.js";
3
+ import { cellOf, valueOf } from "./model.js";
4
+ import { detectDelimiter, parseCSV, readCSV, sheetToCSV } from "./csv.js";
5
+ import { linkTarget } from "./link.js";
6
+ import { defineSheetExtension } from "./extension.js";
7
+ import { defineSheetCellType, optionOf } from "./cell-type.js";
8
+ import { checkWorkbook, emptyWorkbook, migrateWorkbook } from "./document.js";
9
+ import { compareForSort, meetsRule, sortRows, textCollator } from "./data.js";
10
+ import { SheetTransaction, applySheetSteps, compactSheetSteps, sheetKey } from "./transaction.js";
11
+ import { keepCommentAnchors, sheetCommentAnchors } from "./comments.js";
12
+ import { diffWorkbooks, restoreWorkbook } from "./versions.js";
13
+ import { lightThemeColor, plainStyle } from "./paint.js";
14
+ import { sheetLocales, sheetMessagesFor } from "../locale.js";
15
+ import { checkProtection, isLocked, protectionOf } from "./protect.js";
16
+ import { SheetFileError, createFileSource, csvExporter, csvImporter, defineSheetExporter, defineSheetImporter, exportWorkbook, readWorkbook } from "./file.js";
17
+ export { SheetFileError, SheetTransaction, applySheetSteps, boundsOf, cellOf, changeDecimals, checkProtection, checkWorkbook, columnIndex, columnName, compactSheetSteps, compareForSort, createFileSource, csvExporter, csvImporter, defineSheetCellType, defineSheetExporter, defineSheetExtension, defineSheetImporter, detectDelimiter, diffWorkbooks, dropReferences, emptyWorkbook, exportWorkbook, formatCell, formatRange, formatValue, isLocked, keepCommentAnchors, lightThemeColor, linkTarget, meetsRule, migrateWorkbook, moveReferences, optionOf, parseCSV, parseCell, parseInput, parseRange, plainStyle, protectionOf, quoteSheet, readCSV, readWorkbook, renameReferences, renameReferencesAll, restoreWorkbook, sheetCommentAnchors, sheetKey, sheetLocales, sheetMessagesFor, sheetToCSV, shiftFormula, sortRows, splitSheet, textCollator, valueOf };
@@ -0,0 +1,34 @@
1
+ import { parseRange, splitSheet } from "./address.js";
2
+ // src/core/link.ts
3
+ /** A web address, or an e-mail or phone one with something after its scheme. */
4
+ var WEB = /^(?:https?:\/\/|(?:mailto|tel):\S)/i;
5
+ /** Text that reads as a web address, for typed text to become a link. */
6
+ var TYPED = /^(?:https?:\/\/|mailto:)[^\s<>"]+$/i;
7
+ /** Where a link goes, or `null` for one that isn't followed, such as `javascript:`. */
8
+ function linkTarget(link) {
9
+ const text = link?.trim();
10
+ if (!text) return null;
11
+ if (text.startsWith("#")) {
12
+ const { sheet, ref } = splitSheet(text.slice(1));
13
+ const range = parseRange(ref.replaceAll("$", ""));
14
+ return range ? {
15
+ kind: "cell",
16
+ sheet,
17
+ range
18
+ } : null;
19
+ }
20
+ if (WEB.test(text)) try {
21
+ return {
22
+ kind: "web",
23
+ href: new URL(text).href
24
+ };
25
+ } catch {
26
+ return null;
27
+ }
28
+ return null;
29
+ }
30
+ /** Whether typed text is a web address, which then becomes the cell's link. */
31
+ var isTypedLink = (text) => TYPED.test(text.trim()) && !!linkTarget(text);
32
+ export { isTypedLink, linkTarget };
33
+
34
+ //# sourceMappingURL=link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.js","names":[],"sources":["../../src/core/link.ts"],"sourcesContent":["import { parseRange, splitSheet, type SheetRange } from './address'\n\n/*\n * Links. A cell's `link`, or what its `HYPERLINK` formula gives, is a web\n * address or a place in the workbook written `#` and a reference. Only\n * addresses that can't run script are followed.\n */\n\n/** Where a link goes: a web page or app, or cells of the workbook. */\nexport type SheetLinkTarget =\n { kind: 'web'; href: string } | { kind: 'cell'; sheet?: string; range: SheetRange }\n\n/** A web address, or an e-mail or phone one with something after its scheme. */\nconst WEB = /^(?:https?:\\/\\/|(?:mailto|tel):\\S)/i\n/** Text that reads as a web address, for typed text to become a link. */\nconst TYPED = /^(?:https?:\\/\\/|mailto:)[^\\s<>\"]+$/i\n\n/** Where a link goes, or `null` for one that isn't followed, such as `javascript:`. */\nexport function linkTarget(link: string | undefined): SheetLinkTarget | null {\n const text = link?.trim()\n if (!text) return null\n if (text.startsWith('#')) {\n const { sheet, ref } = splitSheet(text.slice(1))\n const range = parseRange(ref.replaceAll('$', ''))\n return range ? { kind: 'cell', sheet, range } : null\n }\n if (WEB.test(text)) {\n try {\n return { kind: 'web', href: new URL(text).href }\n } catch {\n return null\n }\n }\n return null\n}\n\n/** Whether typed text is a web address, which then becomes the cell's link. */\nexport const isTypedLink = (text: string) => TYPED.test(text.trim()) && !!linkTarget(text)\n"],"mappings":";;;AAaA,IAAM,MAAM;;AAEZ,IAAM,QAAQ;;AAGd,SAAgB,WAAW,MAAkD;CAC3E,MAAM,OAAO,MAAM,KAAK;CACxB,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,KAAK,WAAW,GAAG,GAAG;EACxB,MAAM,EAAE,OAAO,QAAQ,WAAW,KAAK,MAAM,CAAC,CAAC;EAC/C,MAAM,QAAQ,WAAW,IAAI,WAAW,KAAK,EAAE,CAAC;EAChD,OAAO,QAAQ;GAAE,MAAM;GAAQ;GAAO;EAAM,IAAI;CAClD;CACA,IAAI,IAAI,KAAK,IAAI,GACf,IAAI;EACF,OAAO;GAAE,MAAM;GAAO,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC;EAAK;CACjD,QAAQ;EACN,OAAO;CACT;CAEF,OAAO;AACT;;AAGA,IAAa,eAAe,SAAiB,MAAM,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,WAAW,IAAI"}
@@ -0,0 +1,105 @@
1
+ import { boundsOf, parseRange } from "./address.js";
2
+ // src/core/merge.ts
3
+ /** The merged ranges of a sheet, read once, with quick lookups by cell. */
4
+ var SheetMerges = class SheetMerges {
5
+ list;
6
+ static EMPTY = new SheetMerges([]);
7
+ /** By row, the merges that cover it. */
8
+ byRow = /* @__PURE__ */ new Map();
9
+ constructor(list) {
10
+ this.list = list;
11
+ for (const b of list) for (let row = b.top; row <= b.bottom; row++) {
12
+ const at = this.byRow.get(row);
13
+ if (at) at.push(b);
14
+ else this.byRow.set(row, [b]);
15
+ }
16
+ }
17
+ get size() {
18
+ return this.list.length;
19
+ }
20
+ /** The merge covering a cell, or `undefined`. */
21
+ at(row, col) {
22
+ const list = this.byRow.get(row);
23
+ if (!list) return void 0;
24
+ for (const b of list) if (col >= b.left && col <= b.right) return b;
25
+ }
26
+ /** The merges that cover a row. */
27
+ inRow(row) {
28
+ return this.byRow.get(row) ?? NONE;
29
+ }
30
+ /** The top left cell of the merge a cell is in, or the cell itself. */
31
+ anchor(point) {
32
+ const b = this.at(point.row, point.col);
33
+ return b ? {
34
+ row: b.top,
35
+ col: b.left
36
+ } : point;
37
+ }
38
+ /** The cell's bounds: its merge, or just itself. */
39
+ cell(point) {
40
+ return this.at(point.row, point.col) ?? {
41
+ top: point.row,
42
+ left: point.col,
43
+ bottom: point.row,
44
+ right: point.col
45
+ };
46
+ }
47
+ /** Grows a range until no merge sticks out of it. */
48
+ expand(range) {
49
+ if (!this.list.length) return range;
50
+ const b = { ...range };
51
+ for (let grew = true; grew;) {
52
+ grew = false;
53
+ for (const m of this.list) {
54
+ if (m.bottom < b.top || m.top > b.bottom || m.right < b.left || m.left > b.right) continue;
55
+ if (m.top < b.top || m.left < b.left || m.bottom > b.bottom || m.right > b.right) {
56
+ b.top = Math.min(b.top, m.top);
57
+ b.left = Math.min(b.left, m.left);
58
+ b.bottom = Math.max(b.bottom, m.bottom);
59
+ b.right = Math.max(b.right, m.right);
60
+ grew = true;
61
+ }
62
+ }
63
+ }
64
+ return b;
65
+ }
66
+ /** The merges that touch a range. */
67
+ touching(range) {
68
+ return this.list.filter((m) => m.bottom >= range.top && m.top <= range.bottom && m.right >= range.left && m.left <= range.right);
69
+ }
70
+ };
71
+ var NONE = [];
72
+ var cache = /* @__PURE__ */ new WeakMap();
73
+ /** A sheet's merges, read once per list. Ranges that aren't ranges, or are one cell, are left out. */
74
+ function mergesOf(sheet) {
75
+ const list = sheet.merges;
76
+ if (!list?.length) return SheetMerges.EMPTY;
77
+ let merges = cache.get(list);
78
+ if (!merges) {
79
+ const bounds = [];
80
+ const rows = /* @__PURE__ */ new Map();
81
+ for (const text of list) {
82
+ const range = parseRange(text);
83
+ if (!range) continue;
84
+ const b = boundsOf(range);
85
+ if (!Number.isFinite(b.bottom) || !Number.isFinite(b.right)) continue;
86
+ if (b.top === b.bottom && b.left === b.right) continue;
87
+ let clash = false;
88
+ for (let row = b.top; row <= b.bottom && !clash; row++) clash = !!rows.get(row)?.some((m) => overlaps(m, b));
89
+ if (clash) continue;
90
+ bounds.push(b);
91
+ for (let row = b.top; row <= b.bottom; row++) {
92
+ const at = rows.get(row);
93
+ if (at) at.push(b);
94
+ else rows.set(row, [b]);
95
+ }
96
+ }
97
+ merges = new SheetMerges(bounds);
98
+ cache.set(list, merges);
99
+ }
100
+ return merges;
101
+ }
102
+ var overlaps = (a, b) => a.bottom >= b.top && a.top <= b.bottom && a.right >= b.left && a.left <= b.right;
103
+ export { SheetMerges, mergesOf, overlaps };
104
+
105
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","names":[],"sources":["../../src/core/merge.ts"],"sourcesContent":["import { boundsOf, parseRange, type SheetBounds, type SheetPoint } from './address'\nimport type { SheetData } from './model'\n\n/*\n * Merged cells. A sheet lists them as ranges (`'A1:C1'`); the cell at the\n * top left of each holds what the merged cell shows, and the cells it\n * covers are kept but not shown.\n */\n\n/** The merged ranges of a sheet, read once, with quick lookups by cell. */\nexport class SheetMerges {\n static readonly EMPTY = new SheetMerges([])\n\n /** By row, the merges that cover it. */\n private byRow = new Map<number, SheetBounds[]>()\n\n constructor(readonly list: readonly SheetBounds[]) {\n for (const b of list)\n for (let row = b.top; row <= b.bottom; row++) {\n const at = this.byRow.get(row)\n if (at) at.push(b)\n else this.byRow.set(row, [b])\n }\n }\n\n get size() {\n return this.list.length\n }\n\n /** The merge covering a cell, or `undefined`. */\n at(row: number, col: number): SheetBounds | undefined {\n const list = this.byRow.get(row)\n if (!list) return undefined\n for (const b of list) if (col >= b.left && col <= b.right) return b\n return undefined\n }\n\n /** The merges that cover a row. */\n inRow(row: number): readonly SheetBounds[] {\n return this.byRow.get(row) ?? NONE\n }\n\n /** The top left cell of the merge a cell is in, or the cell itself. */\n anchor(point: SheetPoint): SheetPoint {\n const b = this.at(point.row, point.col)\n return b ? { row: b.top, col: b.left } : point\n }\n\n /** The cell's bounds: its merge, or just itself. */\n cell(point: SheetPoint): SheetBounds {\n return (\n this.at(point.row, point.col) ?? {\n top: point.row,\n left: point.col,\n bottom: point.row,\n right: point.col,\n }\n )\n }\n\n /** Grows a range until no merge sticks out of it. */\n expand(range: SheetBounds): SheetBounds {\n if (!this.list.length) return range\n const b = { ...range }\n for (let grew = true; grew;) {\n grew = false\n for (const m of this.list) {\n if (m.bottom < b.top || m.top > b.bottom || m.right < b.left || m.left > b.right) continue\n if (m.top < b.top || m.left < b.left || m.bottom > b.bottom || m.right > b.right) {\n b.top = Math.min(b.top, m.top)\n b.left = Math.min(b.left, m.left)\n b.bottom = Math.max(b.bottom, m.bottom)\n b.right = Math.max(b.right, m.right)\n grew = true\n }\n }\n }\n return b\n }\n\n /** The merges that touch a range. */\n touching(range: SheetBounds): SheetBounds[] {\n return this.list.filter(\n (m) =>\n m.bottom >= range.top &&\n m.top <= range.bottom &&\n m.right >= range.left &&\n m.left <= range.right,\n )\n }\n}\n\nconst NONE: readonly SheetBounds[] = []\nconst cache = new WeakMap<readonly string[], SheetMerges>()\n\n/** A sheet's merges, read once per list. Ranges that aren't ranges, or are one cell, are left out. */\nexport function mergesOf(sheet: SheetData): SheetMerges {\n const list = sheet.merges\n if (!list?.length) return SheetMerges.EMPTY\n let merges = cache.get(list)\n if (!merges) {\n const bounds: SheetBounds[] = []\n // by row, to find overlaps without comparing every pair\n const rows = new Map<number, SheetBounds[]>()\n for (const text of list) {\n const range = parseRange(text)\n if (!range) continue\n const b = boundsOf(range)\n if (!Number.isFinite(b.bottom) || !Number.isFinite(b.right)) continue\n if (b.top === b.bottom && b.left === b.right) continue\n // a merge overlapping one before it is left out\n let clash = false\n for (let row = b.top; row <= b.bottom && !clash; row++)\n clash = !!rows.get(row)?.some((m) => overlaps(m, b))\n if (clash) continue\n bounds.push(b)\n for (let row = b.top; row <= b.bottom; row++) {\n const at = rows.get(row)\n if (at) at.push(b)\n else rows.set(row, [b])\n }\n }\n merges = new SheetMerges(bounds)\n cache.set(list, merges)\n }\n return merges\n}\n\nexport const overlaps = (a: SheetBounds, b: SheetBounds) =>\n a.bottom >= b.top && a.top <= b.bottom && a.right >= b.left && a.left <= b.right\n"],"mappings":";;;AAUA,IAAa,cAAb,MAAa,YAAY;CAMF;CALrB,OAAgB,QAAQ,IAAI,YAAY,CAAC,CAAC;;CAG1C,wBAAgB,IAAI,IAA2B;CAE/C,YAAY,MAAuC;EAA9B,KAAA,OAAA;EACnB,KAAK,MAAM,KAAK,MACd,KAAK,IAAI,MAAM,EAAE,KAAK,OAAO,EAAE,QAAQ,OAAO;GAC5C,MAAM,KAAK,KAAK,MAAM,IAAI,GAAG;GAC7B,IAAI,IAAI,GAAG,KAAK,CAAC;QACZ,KAAK,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC;EAC9B;CACJ;CAEA,IAAI,OAAO;EACT,OAAO,KAAK,KAAK;CACnB;;CAGA,GAAG,KAAa,KAAsC;EACpD,MAAM,OAAO,KAAK,MAAM,IAAI,GAAG;EAC/B,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,KAAK,MAAM,KAAK,MAAM,IAAI,OAAO,EAAE,QAAQ,OAAO,EAAE,OAAO,OAAO;CAEpE;;CAGA,MAAM,KAAqC;EACzC,OAAO,KAAK,MAAM,IAAI,GAAG,KAAK;CAChC;;CAGA,OAAO,OAA+B;EACpC,MAAM,IAAI,KAAK,GAAG,MAAM,KAAK,MAAM,GAAG;EACtC,OAAO,IAAI;GAAE,KAAK,EAAE;GAAK,KAAK,EAAE;EAAK,IAAI;CAC3C;;CAGA,KAAK,OAAgC;EACnC,OACE,KAAK,GAAG,MAAM,KAAK,MAAM,GAAG,KAAK;GAC/B,KAAK,MAAM;GACX,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,OAAO,MAAM;EACf;CAEJ;;CAGA,OAAO,OAAiC;EACtC,IAAI,CAAC,KAAK,KAAK,QAAQ,OAAO;EAC9B,MAAM,IAAI,EAAE,GAAG,MAAM;EACrB,KAAK,IAAI,OAAO,MAAM,OAAO;GAC3B,OAAO;GACP,KAAK,MAAM,KAAK,KAAK,MAAM;IACzB,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO;IAClF,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO;KAChF,EAAE,MAAM,KAAK,IAAI,EAAE,KAAK,EAAE,GAAG;KAC7B,EAAE,OAAO,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI;KAChC,EAAE,SAAS,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;KACtC,EAAE,QAAQ,KAAK,IAAI,EAAE,OAAO,EAAE,KAAK;KACnC,OAAO;IACT;GACF;EACF;EACA,OAAO;CACT;;CAGA,SAAS,OAAmC;EAC1C,OAAO,KAAK,KAAK,QACd,MACC,EAAE,UAAU,MAAM,OAClB,EAAE,OAAO,MAAM,UACf,EAAE,SAAS,MAAM,QACjB,EAAE,QAAQ,MAAM,KACpB;CACF;AACF;AAEA,IAAM,OAA+B,CAAC;AACtC,IAAM,wBAAQ,IAAI,QAAwC;;AAG1D,SAAgB,SAAS,OAA+B;CACtD,MAAM,OAAO,MAAM;CACnB,IAAI,CAAC,MAAM,QAAQ,OAAO,YAAY;CACtC,IAAI,SAAS,MAAM,IAAI,IAAI;CAC3B,IAAI,CAAC,QAAQ;EACX,MAAM,SAAwB,CAAC;EAE/B,MAAM,uBAAO,IAAI,IAA2B;EAC5C,KAAK,MAAM,QAAQ,MAAM;GACvB,MAAM,QAAQ,WAAW,IAAI;GAC7B,IAAI,CAAC,OAAO;GACZ,MAAM,IAAI,SAAS,KAAK;GACxB,IAAI,CAAC,OAAO,SAAS,EAAE,MAAM,KAAK,CAAC,OAAO,SAAS,EAAE,KAAK,GAAG;GAC7D,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO;GAE9C,IAAI,QAAQ;GACZ,KAAK,IAAI,MAAM,EAAE,KAAK,OAAO,EAAE,UAAU,CAAC,OAAO,OAC/C,QAAQ,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,EAAE,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC;GACrD,IAAI,OAAO;GACX,OAAO,KAAK,CAAC;GACb,KAAK,IAAI,MAAM,EAAE,KAAK,OAAO,EAAE,QAAQ,OAAO;IAC5C,MAAM,KAAK,KAAK,IAAI,GAAG;IACvB,IAAI,IAAI,GAAG,KAAK,CAAC;SACZ,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC;GACxB;EACF;EACA,SAAS,IAAI,YAAY,MAAM;EAC/B,MAAM,IAAI,MAAM,MAAM;CACxB;CACA,OAAO;AACT;AAEA,IAAa,YAAY,GAAgB,MACvC,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE"}
@@ -0,0 +1,48 @@
1
+ // src/core/model.ts
2
+ var DEFAULT_ROWS = 1e3;
3
+ /** The cell as an object, or `undefined` when it is empty. */
4
+ function cellOf(input) {
5
+ if (input == null || input === "") return void 0;
6
+ return typeof input === "object" ? input : { value: input };
7
+ }
8
+ /** The value of a cell as written in the data. */
9
+ function valueOf(input) {
10
+ if (input == null) return null;
11
+ return typeof input === "object" ? input.value ?? null : input;
12
+ }
13
+ /** Whether a cell has no value (it may still have a style). */
14
+ var isBlank = (input) => {
15
+ const value = valueOf(input);
16
+ return value == null || value === "";
17
+ };
18
+ /** The shortest way to write a cell: a bare value when that is all it has. */
19
+ function compactCell(cell) {
20
+ if (!cell) return void 0;
21
+ let only;
22
+ let count = 0;
23
+ for (const key in cell) {
24
+ const value = cell[key];
25
+ if (value === void 0) continue;
26
+ count++;
27
+ if (key === "value") only = value;
28
+ }
29
+ if (count === 0) return void 0;
30
+ if (count === 1 && only !== void 0) return only === "" ? void 0 : only;
31
+ const out = {};
32
+ for (const key in cell) {
33
+ const value = cell[key];
34
+ if (value !== void 0) out[key] = value;
35
+ }
36
+ return out;
37
+ }
38
+ /** How many rows a sheet has, counting its data. */
39
+ var rowCountOf = (sheet) => Math.max(sheet.rowCount ?? 1e3, sheet.cells.length);
40
+ /** How many columns a sheet has, counting its widest row. */
41
+ function colCountOf(sheet) {
42
+ let widest = 0;
43
+ for (const row of sheet.cells) if (row && row.length > widest) widest = row.length;
44
+ return Math.max(sheet.colCount ?? 26, widest);
45
+ }
46
+ export { DEFAULT_ROWS, cellOf, colCountOf, compactCell, isBlank, rowCountOf, valueOf };
47
+
48
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","names":[],"sources":["../../src/core/model.ts"],"sourcesContent":["import type { Tone } from 'dou-ui'\n\n/**\n * What a cell holds. A string starting with `=` is a formula; text that\n * starts with `=` is written `'=…` and shows without the `'`.\n */\nexport type SheetValue = string | number | boolean | null\n\n/** A cell with more than a value. */\nexport interface SheetCell {\n value?: SheetValue\n /** The name of a style in the workbook's `styles`. */\n style?: string\n /**\n * How a number or date shows, such as `0.00`, `#,##0`, `0%`, `¥#,##0.00`\n * or `yyyy-mm-dd`. The value itself is kept as it is.\n */\n format?: string\n /** A note on the cell. */\n note?: string\n /**\n * The id of the comment thread about the cell, one of `v-model:comments`.\n * Copying a cell leaves it behind; cutting moves it.\n */\n comment?: string\n /**\n * Where the cell links to: a web address (`https:`, `http:`, `mailto:`\n * or `tel:`), or a place in the workbook written `#` and a reference,\n * such as `#A20` or `#'汇总'!B2`. Other addresses are not followed.\n */\n link?: string\n /**\n * What kind of cell it is: `date`, `select`, `checkbox`, a type an\n * extension adds, or a name in the workbook's `types`. It decides how the\n * value shows and how it is edited.\n */\n type?: string\n}\n\n/** A cell as written in the data: a bare value, or a cell with a style or format. */\nexport type SheetCellInput = SheetValue | SheetCell | undefined\n\n/** One row of cells, left to right. Empty cells can be left out (`undefined` or `null`). */\nexport type SheetRow = SheetCellInput[]\n\nexport interface SheetFreeze {\n /** Rows kept at the top while scrolling. */\n rows?: number\n /** Columns kept at the left while scrolling. */\n cols?: number\n}\n\n/** One sheet of a workbook. */\nexport interface SheetData {\n /**\n * A stable name for the sheet that changes and collaboration refer to.\n * Without one they use `name`; give an `id` when sheets can be renamed\n * while changes are on their way, e.g. from another client.\n */\n id?: string\n /** Shown on the sheet's tab and used by formulas to refer to it. Unique in the workbook. */\n name: string\n /** The cells, row by row. Rows and cells past the data can be left out. */\n cells: (SheetRow | null | undefined)[]\n /** How many rows the sheet has; at least the rows in `cells`. Defaults to 1000. */\n rowCount?: number\n /** How many columns the sheet has; at least the widest row. Defaults to 26. */\n colCount?: number\n /** Row heights that differ from the default, by row number; `0` hides the row. */\n rowSizes?: Record<number, number>\n /** Column widths that differ from the default, by column number; `0` hides the column. */\n colSizes?: Record<number, number>\n freeze?: SheetFreeze\n /**\n * Merged cells, as ranges such as `'A1:C1'`. The top left cell of each\n * holds what it shows; the cells it covers keep their data but are not\n * shown. Merges must not overlap.\n */\n merges?: string[]\n /** A filter on a range: rows whose cells don't match its rules are hidden. */\n filter?: SheetFilter\n /**\n * Rows or columns that fold away together, such as the detail rows under\n * a subtotal. Groups on the same axis nest or stay apart, at most eight\n * deep.\n */\n groups?: SheetGroup[]\n /**\n * Where a group's heading or subtotal is, and so its button: on the row\n * above it (the column left of it) with `'before'`, the default, or below\n * it (right of it) with `'after'`.\n */\n groupSummary?: 'before' | 'after'\n /**\n * Looks cells take on when they meet a rule, such as a red fill for\n * amounts over budget. Where two formats give the same part of the look,\n * the earlier one wins.\n */\n conditionalFormats?: SheetConditionalFormat[]\n /**\n * What cells may hold, such as one of a list or a number from 1 to 5.\n * Where two rules cover a cell, the earlier one applies.\n */\n validations?: SheetValidation[]\n /**\n * Keeps the locked cells from being changed: every cell but those whose\n * style says `locked: false`, such as the blanks of a form to fill in.\n * Rows, columns and the sheet's rules can't be changed either, except\n * what the options allow. Code changes it with `tr.protectSheet`.\n */\n protect?: boolean | SheetProtection\n}\n\n/**\n * What people may still do on a protected sheet besides changing unlocked\n * cells. Everything is `false` by default.\n */\nexport interface SheetProtection {\n /** Change the look and number format of locked cells, and merge cells. */\n formatCells?: boolean\n /** Change column widths and hide columns. */\n formatColumns?: boolean\n /** Change row heights and hide rows. */\n formatRows?: boolean\n insertRows?: boolean\n insertColumns?: boolean\n deleteRows?: boolean\n deleteColumns?: boolean\n /** Use the sheet's filter to pick which rows show. Sorting moves cells, so only unlocked ones. */\n filter?: boolean\n}\n\n/** Rows or columns that fold away together. */\nexport interface SheetGroup {\n /** Whole rows such as `'3:8'`, or whole columns such as `'B:D'`. */\n range: string\n /** Folded: its rows or columns are hidden until it is expanded. */\n collapsed?: boolean\n}\n\n/**\n * A look for the cells of `range` that meet a rule. The rule is one of\n * `when`, `formula`, `top`, `bottom`, `average`, `duplicates` or `unique`, with\n * `style` as the look; or the cells are shaded by their numbers, with\n * `scale` or `bar`.\n */\nexport type SheetConditionalFormat =\n | SheetConditionalWhen\n | SheetConditionalFormula\n | SheetConditionalRank\n | SheetConditionalAverage\n | SheetConditionalDuplicates\n | SheetConditionalUnique\n | SheetConditionalScale\n | SheetConditionalBar\n\ninterface SheetConditionalBase {\n /**\n * The cells, such as `'D2:D40'`, or several ranges taken as one, such as\n * `['D2:D40', 'G2:G40']`.\n */\n range: SheetRuleRange\n}\n\n/**\n * The cells of a rule: a range, or several ranges taken as one. A rule's\n * formula is written for the top left cell of the first.\n */\nexport type SheetRuleRange = string | string[]\n\n/** A look: a style's name in the workbook's `styles`, or a style. */\nexport type SheetConditionalStyle = string | SheetStyle\n\n/** Cells whose value meets a condition, e.g. `{ op: 'gt', value: 100 }`. */\nexport interface SheetConditionalWhen extends SheetConditionalBase {\n when: SheetCondition\n style: SheetConditionalStyle\n}\n\n/**\n * Cells for which a formula comes to `TRUE` (or a number other than 0).\n * It is written for the range's top left cell and moves with each cell, as\n * a copied formula does: `'=$E2>$D2'` on `'A2:F40'` checks each row's E and D.\n */\nexport interface SheetConditionalFormula extends SheetConditionalBase {\n formula: string\n style: SheetConditionalStyle\n}\n\n/** The largest (`top`) or smallest (`bottom`) numbers of the range: a count, or a percent of them. */\nexport type SheetConditionalRank = SheetConditionalBase & {\n /** With `percent`, a percent of the numbers, e.g. `10` for the top 10%. */\n percent?: boolean\n style: SheetConditionalStyle\n} & ({ top: number; bottom?: never } | { bottom: number; top?: never })\n\n/** Numbers above or below the range's average. */\nexport interface SheetConditionalAverage extends SheetConditionalBase {\n average: 'above' | 'below'\n style: SheetConditionalStyle\n}\n\n/** Values that show up more than once in the range, compared as they show, without case. */\nexport interface SheetConditionalDuplicates extends SheetConditionalBase {\n duplicates: true\n style: SheetConditionalStyle\n}\n\n/** Values that show up only once in the range, compared as they show, without case. */\nexport interface SheetConditionalUnique extends SheetConditionalBase {\n unique: true\n style: SheetConditionalStyle\n}\n\n/** A tone, or any CSS color. */\nexport type SheetColor = Tone | (string & {})\n\n/**\n * Fills each number's cell with a color between two or three, from the\n * smallest number to the largest (through the middle one with three), e.g.\n * `['danger', 'warning', 'success']`.\n */\nexport interface SheetConditionalScale extends SheetConditionalBase {\n scale: [SheetColor, SheetColor] | [SheetColor, SheetColor, SheetColor]\n}\n\n/**\n * Draws a bar in each number's cell, as long as the number is large\n * against the others. Negative numbers draw to the left of zero, in the\n * same color. `true` uses the neutral tone.\n */\nexport interface SheetConditionalBar extends SheetConditionalBase {\n bar: true | SheetColor\n}\n\n/**\n * What the cells of `range` may hold: one of a `list`, a value that meets\n * a condition (`when`), or one for which a `formula` comes to `TRUE`.\n * Empty cells always fit.\n */\nexport type SheetValidation = SheetValidationList | SheetValidationWhen | SheetValidationFormula\n\ninterface SheetValidationBase {\n /** The cells, such as `'D2:D40'`, or several ranges, such as `['E2:E9', 'G2:J9']`. */\n range: SheetRuleRange\n /**\n * Whether a typed value that doesn't fit is refused, `true` by default.\n * With `false` it is kept and marked. Pasted and filled values are always\n * kept and marked.\n */\n strict?: boolean\n /** Said when a value doesn't fit; by default the rule in words. */\n message?: string\n /** Shown beside the cell while it is the active one, e.g. what to enter. */\n prompt?: string\n}\n\n/**\n * One of a list: the values, or a reference to cells holding them, such as\n * `'=$H$2:$H$9'` or `'=城市!A2:A20'`. The cells get a list to pick from.\n */\nexport interface SheetValidationList extends SheetValidationBase {\n list: SheetSelectOption[] | string\n}\n\n/**\n * A value that meets a condition, e.g. `{ op: 'between', value: 1, to: 5 }`.\n * A number in the condition asks for a number; with `integer`, a whole one.\n */\nexport interface SheetValidationWhen extends SheetValidationBase {\n when: SheetCondition\n integer?: boolean\n}\n\n/**\n * A value for which a formula comes to `TRUE` (or a number other than 0).\n * It is written for the range's top left cell and moves with each cell:\n * `'=COUNTIF($A$2:$A$40, A2) = 1'` on `'A2:A40'` asks for values not repeated.\n */\nexport interface SheetValidationFormula extends SheetValidationBase {\n formula: string\n}\n\n/**\n * A filter on a range of rows. The range's first row holds the headings,\n * which get the filter buttons; the rows below are filtered. Rows hidden by\n * a filter are not stored as hidden: clearing the rules shows them again.\n */\nexport interface SheetFilter {\n /** The range, headings included, such as `'A1:E40'`. */\n range: string\n /** The rule of each filtered column, by column number. */\n columns?: Record<number, SheetFilterRule>\n /** The column the rows were last sorted by from the filter, shown on its button. */\n sorted?: { col: number; order: SheetSortOrder }\n}\n\n/**\n * Which rows a column keeps: those whose shown text is one of `values` (an\n * empty cell is `''`), or those that meet `condition`.\n */\nexport type SheetFilterRule = { values: string[] } | { condition: SheetCondition }\n\n/**\n * A condition on a cell; numbers compare as numbers, text without case.\n * `between` and `notBetween` take `value` and `to`, both ends included.\n */\nexport interface SheetCondition {\n op:\n | 'eq'\n | 'ne'\n | 'gt'\n | 'ge'\n | 'lt'\n | 'le'\n | 'between'\n | 'notBetween'\n | 'contains'\n | 'notContains'\n | 'begins'\n | 'ends'\n | 'blank'\n | 'notBlank'\n value?: SheetValue\n /** The other end for `between` and `notBetween`. */\n to?: SheetValue\n}\n\nexport type SheetSortOrder = 'asc' | 'desc'\n\n/** How cells that use a style look. Tones follow the color mode; colors are used as given. */\nexport interface SheetStyle {\n bold?: boolean\n italic?: boolean\n underline?: boolean\n strike?: boolean\n /** Text in a tone's color. */\n tone?: Tone\n /** A soft fill in a tone's color. */\n fill?: Tone\n /** Any CSS color for the text; wins over `tone`. */\n color?: string\n /** Any CSS color for the fill; wins over `fill`. */\n background?: string\n /** Defaults to the value's own: numbers right, true and false centered, text left. */\n align?: 'left' | 'center' | 'right'\n valign?: 'top' | 'middle' | 'bottom'\n /** Wrap long text onto more lines instead of cutting it off. */\n wrap?: boolean\n /**\n * With `false`, the cell can still be changed while its sheet is\n * protected. Cells are locked by default.\n */\n locked?: boolean\n}\n\n/**\n * A cell type with its settings, which cells refer to by name, e.g.\n * `{ type: 'select', options: ['未开始', '进行中', '已完成'] }`.\n */\nexport interface SheetTypeSetup {\n /** The cell type it sets up: `select`, or a type an extension adds. */\n type: string\n /** For `select`: the values to pick from, each a value or a value with a tone. */\n options?: SheetSelectOption[]\n /** Settings of types from extensions. */\n [key: string]: unknown\n}\n\n/** A value of a `select` cell; with a `tone` it shows as a soft tag in that tone. */\nexport type SheetSelectOption = SheetValue | { value: SheetValue; tone?: Tone }\n\nexport interface SheetWorkbook {\n /** The version of the format; see `migrateWorkbook`. */\n version: 1\n sheets: SheetData[]\n /** Named styles, which cells refer to by name. */\n styles?: Record<string, SheetStyle>\n /** Named cell types with their settings, which cells refer to with `type`. */\n types?: Record<string, SheetTypeSetup>\n /**\n * Keeps the sheets as they are: none can be added, removed, moved or\n * renamed. Code changes it with `tr.updateBook({ protect })`.\n */\n protect?: boolean\n}\n\nexport const DEFAULT_ROWS = 1000\nexport const DEFAULT_COLS = 26\n\n/** The cell as an object, or `undefined` when it is empty. */\nexport function cellOf(input: SheetCellInput): SheetCell | undefined {\n if (input == null || input === '') return undefined\n return typeof input === 'object' ? input : { value: input }\n}\n\n/** The value of a cell as written in the data. */\nexport function valueOf(input: SheetCellInput): SheetValue {\n if (input == null) return null\n return typeof input === 'object' ? (input.value ?? null) : input\n}\n\n/** Whether a cell has no value (it may still have a style). */\nexport const isBlank = (input: SheetCellInput) => {\n const value = valueOf(input)\n return value == null || value === ''\n}\n\n/** The shortest way to write a cell: a bare value when that is all it has. */\nexport function compactCell(cell: SheetCell | undefined): SheetCellInput {\n if (!cell) return undefined\n let only: SheetValue | undefined\n let count = 0\n for (const key in cell) {\n const value = cell[key as keyof SheetCell]\n if (value === undefined) continue\n count++\n if (key === 'value') only = value as SheetValue\n }\n if (count === 0) return undefined\n if (count === 1 && only !== undefined) return only === '' ? undefined : only\n const out: SheetCell = {}\n for (const key in cell) {\n const value = cell[key as keyof SheetCell]\n if (value !== undefined) (out as Record<string, unknown>)[key] = value\n }\n return out\n}\n\n/** The cell at a row and column. */\nexport const cellAt = (sheet: SheetData, row: number, col: number): SheetCellInput =>\n sheet.cells[row]?.[col] ?? undefined\n\n/** How many rows a sheet has, counting its data. */\nexport const rowCountOf = (sheet: SheetData) =>\n Math.max(sheet.rowCount ?? DEFAULT_ROWS, sheet.cells.length)\n\n/** How many columns a sheet has, counting its widest row. */\nexport function colCountOf(sheet: SheetData) {\n let widest = 0\n for (const row of sheet.cells) if (row && row.length > widest) widest = row.length\n return Math.max(sheet.colCount ?? DEFAULT_COLS, widest)\n}\n"],"mappings":";AAmYA,IAAa,eAAe;;AAI5B,SAAgB,OAAO,OAA8C;CACnE,IAAI,SAAS,QAAQ,UAAU,IAAI,OAAO,KAAA;CAC1C,OAAO,OAAO,UAAU,WAAW,QAAQ,EAAE,OAAO,MAAM;AAC5D;;AAGA,SAAgB,QAAQ,OAAmC;CACzD,IAAI,SAAS,MAAM,OAAO;CAC1B,OAAO,OAAO,UAAU,WAAY,MAAM,SAAS,OAAQ;AAC7D;;AAGA,IAAa,WAAW,UAA0B;CAChD,MAAM,QAAQ,QAAQ,KAAK;CAC3B,OAAO,SAAS,QAAQ,UAAU;AACpC;;AAGA,SAAgB,YAAY,MAA6C;CACvE,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,IAAI;CACJ,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,KAAK;EACnB,IAAI,UAAU,KAAA,GAAW;EACzB;EACA,IAAI,QAAQ,SAAS,OAAO;CAC9B;CACA,IAAI,UAAU,GAAG,OAAO,KAAA;CACxB,IAAI,UAAU,KAAK,SAAS,KAAA,GAAW,OAAO,SAAS,KAAK,KAAA,IAAY;CACxE,MAAM,MAAiB,CAAC;CACxB,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,KAAK;EACnB,IAAI,UAAU,KAAA,GAAW,IAAiC,OAAO;CACnE;CACA,OAAO;AACT;;AAOA,IAAa,cAAc,UACzB,KAAK,IAAI,MAAM,YAAA,KAA0B,MAAM,MAAM,MAAM;;AAG7D,SAAgB,WAAW,OAAkB;CAC3C,IAAI,SAAS;CACb,KAAK,MAAM,OAAO,MAAM,OAAO,IAAI,OAAO,IAAI,SAAS,QAAQ,SAAS,IAAI;CAC5E,OAAO,KAAK,IAAI,MAAM,YAAA,IAA0B,MAAM;AACxD"}
@@ -0,0 +1,44 @@
1
+ // src/core/nav.ts
2
+ /** One step from `index` along an axis, skipping hidden rows or columns; stays put at the edge. */
3
+ function stepIndex(axis, index, step) {
4
+ for (let i = index + step; i >= 0 && i < axis.count; i += step) if (!axis.hidden(i)) return i;
5
+ return index;
6
+ }
7
+ /**
8
+ * Where `mod` + an arrow goes: to the edge of the block of filled cells the
9
+ * cell is in, or on to the next filled cell, or to the sheet's edge.
10
+ */
11
+ function edgeIndex(axis, index, step, filled) {
12
+ const next = stepIndex(axis, index, step);
13
+ if (next === index) return index;
14
+ let at = next;
15
+ if (filled(index) && filled(next)) for (let i = next;;) {
16
+ const after = stepIndex(axis, i, step);
17
+ if (after === i || !filled(after)) return i;
18
+ i = after;
19
+ }
20
+ while (!filled(at)) {
21
+ const after = stepIndex(axis, at, step);
22
+ if (after === at) return at;
23
+ at = after;
24
+ }
25
+ return at;
26
+ }
27
+ /** A point moved one cell, or to the data edge with `jump`. */
28
+ function movePoint(point, direction, axes, filled, jump = false) {
29
+ const vertical = direction === "up" || direction === "down";
30
+ const step = direction === "up" || direction === "left" ? -1 : 1;
31
+ const axis = vertical ? axes.rows : axes.cols;
32
+ const index = vertical ? point.row : point.col;
33
+ const to = jump ? edgeIndex(axis, index, step, (i) => vertical ? filled(i, point.col) : filled(point.row, i)) : stepIndex(axis, index, step);
34
+ return vertical ? {
35
+ row: to,
36
+ col: point.col
37
+ } : {
38
+ row: point.row,
39
+ col: to
40
+ };
41
+ }
42
+ export { edgeIndex, movePoint, stepIndex };
43
+
44
+ //# sourceMappingURL=nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav.js","names":[],"sources":["../../src/core/nav.ts"],"sourcesContent":["import type { SheetPoint } from './address'\nimport type { SheetAxis } from './axis'\n\n/** One step from `index` along an axis, skipping hidden rows or columns; stays put at the edge. */\nexport function stepIndex(axis: SheetAxis, index: number, step: 1 | -1) {\n for (let i = index + step; i >= 0 && i < axis.count; i += step) if (!axis.hidden(i)) return i\n return index\n}\n\n/**\n * Where `mod` + an arrow goes: to the edge of the block of filled cells the\n * cell is in, or on to the next filled cell, or to the sheet's edge.\n */\nexport function edgeIndex(\n axis: SheetAxis,\n index: number,\n step: 1 | -1,\n filled: (index: number) => boolean,\n) {\n const next = stepIndex(axis, index, step)\n if (next === index) return index\n let at = next\n if (filled(index) && filled(next)) {\n // to the last filled cell of this block\n for (let i = next; ;) {\n const after = stepIndex(axis, i, step)\n if (after === i || !filled(after)) return i\n i = after\n }\n }\n // on to the next filled cell, or the edge\n while (!filled(at)) {\n const after = stepIndex(axis, at, step)\n if (after === at) return at\n at = after\n }\n return at\n}\n\nexport type SheetDirection = 'up' | 'down' | 'left' | 'right'\n\n/** A point moved one cell, or to the data edge with `jump`. */\nexport function movePoint(\n point: SheetPoint,\n direction: SheetDirection,\n axes: { rows: SheetAxis; cols: SheetAxis },\n filled: (row: number, col: number) => boolean,\n jump = false,\n): SheetPoint {\n const vertical = direction === 'up' || direction === 'down'\n const step = direction === 'up' || direction === 'left' ? -1 : 1\n const axis = vertical ? axes.rows : axes.cols\n const index = vertical ? point.row : point.col\n const to = jump\n ? edgeIndex(axis, index, step, (i) => (vertical ? filled(i, point.col) : filled(point.row, i)))\n : stepIndex(axis, index, step)\n return vertical ? { row: to, col: point.col } : { row: point.row, col: to }\n}\n"],"mappings":";;AAIA,SAAgB,UAAU,MAAiB,OAAe,MAAc;CACtE,KAAK,IAAI,IAAI,QAAQ,MAAM,KAAK,KAAK,IAAI,KAAK,OAAO,KAAK,MAAM,IAAI,CAAC,KAAK,OAAO,CAAC,GAAG,OAAO;CAC5F,OAAO;AACT;;;;;AAMA,SAAgB,UACd,MACA,OACA,MACA,QACA;CACA,MAAM,OAAO,UAAU,MAAM,OAAO,IAAI;CACxC,IAAI,SAAS,OAAO,OAAO;CAC3B,IAAI,KAAK;CACT,IAAI,OAAO,KAAK,KAAK,OAAO,IAAI,GAE9B,KAAK,IAAI,IAAI,QAAS;EACpB,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI;EACrC,IAAI,UAAU,KAAK,CAAC,OAAO,KAAK,GAAG,OAAO;EAC1C,IAAI;CACN;CAGF,OAAO,CAAC,OAAO,EAAE,GAAG;EAClB,MAAM,QAAQ,UAAU,MAAM,IAAI,IAAI;EACtC,IAAI,UAAU,IAAI,OAAO;EACzB,KAAK;CACP;CACA,OAAO;AACT;;AAKA,SAAgB,UACd,OACA,WACA,MACA,QACA,OAAO,OACK;CACZ,MAAM,WAAW,cAAc,QAAQ,cAAc;CACrD,MAAM,OAAO,cAAc,QAAQ,cAAc,SAAS,KAAK;CAC/D,MAAM,OAAO,WAAW,KAAK,OAAO,KAAK;CACzC,MAAM,QAAQ,WAAW,MAAM,MAAM,MAAM;CAC3C,MAAM,KAAK,OACP,UAAU,MAAM,OAAO,OAAO,MAAO,WAAW,OAAO,GAAG,MAAM,GAAG,IAAI,OAAO,MAAM,KAAK,CAAC,CAAE,IAC5F,UAAU,MAAM,OAAO,IAAI;CAC/B,OAAO,WAAW;EAAE,KAAK;EAAI,KAAK,MAAM;CAAI,IAAI;EAAE,KAAK,MAAM;EAAK,KAAK;CAAG;AAC5E"}
@@ -0,0 +1,106 @@
1
+ import { contrastRatio, dark, flattenTokens, light, mixColors, parseColor } from "@dou-ui/theme";
2
+ // src/core/paint.ts
3
+ var tokens = new Map(flattenTokens(light));
4
+ /** Text for light backgrounds and for dark ones: each theme's own text color. */
5
+ var INK = tokens.get("--dou-color-text") ?? "#171717";
6
+ var PAPER = new Map(flattenTokens(dark)).get("--dou-color-text") ?? "#ededed";
7
+ /**
8
+ * The text color that reads on a background of a set color, whatever the
9
+ * page's theme: dark on light backgrounds, light on dark ones. `undefined`
10
+ * for a color it can't work out, such as a theme variable, which follows
11
+ * the theme along with the text.
12
+ */
13
+ function textOn(background) {
14
+ if (background.includes("var(")) return void 0;
15
+ const color = lightThemeColor(background);
16
+ try {
17
+ return contrastRatio(color, INK) >= contrastRatio(color, PAPER) ? INK : PAPER;
18
+ } catch {
19
+ return;
20
+ }
21
+ }
22
+ /** A tone's text color, as `is-tone-*` colors it. */
23
+ var TONE_TEXT = (tone) => `var(--dou-color-${tone}-text)`;
24
+ /** A tone's soft fill, as `is-fill-*` colors it. */
25
+ var TONE_FILL = {
26
+ neutral: "color-mix(in oklab, var(--dou-color-text) 6%, var(--dou-color-bg-surface))",
27
+ primary: "color-mix(in oklab, var(--dou-color-primary) 12%, var(--dou-color-bg-surface))",
28
+ success: "color-mix(in oklab, var(--dou-color-success) 12%, var(--dou-color-bg-surface))",
29
+ warning: "color-mix(in oklab, var(--dou-color-warning) 14%, var(--dou-color-bg-surface))",
30
+ danger: "color-mix(in oklab, var(--dou-color-danger) 12%, var(--dou-color-bg-surface))"
31
+ };
32
+ /** The arguments of a CSS function, split at the commas outside brackets. */
33
+ function args(text) {
34
+ const out = [];
35
+ let depth = 0;
36
+ let start = 0;
37
+ for (let i = 0; i < text.length; i++) {
38
+ const ch = text[i];
39
+ if (ch === "(") depth++;
40
+ else if (ch === ")") depth--;
41
+ else if (ch === "," && depth === 0) {
42
+ out.push(text.slice(start, i).trim());
43
+ start = i + 1;
44
+ }
45
+ }
46
+ out.push(text.slice(start).trim());
47
+ return out;
48
+ }
49
+ /** A color of `color-mix` with its share, such as `red 20%`. */
50
+ function mixPart(arg) {
51
+ const m = /^(.*?)\s+([\d.]+)%$/.exec(arg);
52
+ return m ? {
53
+ color: m[1],
54
+ amount: Number(m[2]) / 100
55
+ } : { color: arg };
56
+ }
57
+ /**
58
+ * A color as the light theme paints it, as `#rrggbb` where it can be
59
+ * worked out: theme variables and `color-mix(in oklab, …)` of them. Other
60
+ * colors, such as `red`, are given back as they are.
61
+ */
62
+ function lightThemeColor(css) {
63
+ const text = css.trim();
64
+ const variable = /^var\(\s*(--[\w-]+)\s*(?:,\s*(.+))?\)$/.exec(text);
65
+ if (variable) {
66
+ const value = tokens.get(variable[1]) ?? variable[2];
67
+ return value ? lightThemeColor(value) : text;
68
+ }
69
+ const mix = /^color-mix\(\s*in\s+oklab\s*,(.*)\)$/i.exec(text);
70
+ if (mix) {
71
+ const [first, second] = args(mix[1]);
72
+ if (!first || !second) return text;
73
+ const a = mixPart(first);
74
+ const b = mixPart(second);
75
+ const amount = a.amount ?? (b.amount != null ? 1 - b.amount : .5);
76
+ const from = lightThemeColor(a.color);
77
+ const to = lightThemeColor(b.color);
78
+ try {
79
+ return mixColors(from, to, amount);
80
+ } catch {
81
+ return text;
82
+ }
83
+ }
84
+ try {
85
+ parseColor(text);
86
+ return text.toLowerCase();
87
+ } catch {
88
+ return text;
89
+ }
90
+ }
91
+ /**
92
+ * A style with its `tone` and `fill` as the colors they are in the light
93
+ * theme, in `color` and `background`, and every color plain.
94
+ */
95
+ function plainStyle(style) {
96
+ const { tone, fill, ...rest } = style;
97
+ const out = { ...rest };
98
+ const color = rest.color ?? (tone ? TONE_TEXT(tone) : void 0);
99
+ const background = rest.background ?? (fill ? TONE_FILL[fill] : void 0);
100
+ if (color) out.color = lightThemeColor(color);
101
+ if (background) out.background = lightThemeColor(background);
102
+ return out;
103
+ }
104
+ export { lightThemeColor, plainStyle, textOn };
105
+
106
+ //# sourceMappingURL=paint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paint.js","names":[],"sources":["../../src/core/paint.ts"],"sourcesContent":["import { contrastRatio, dark, flattenTokens, light, mixColors, parseColor } from '@dou-ui/theme'\nimport type { SheetStyle } from './model'\n\n/*\n * Styles in plain colors, for files: a tone, which the grid colors from\n * the page's theme, becomes the color it has in the light theme, as a\n * file opened elsewhere can't follow the page.\n */\n\nconst tokens = new Map(flattenTokens(light))\n\n/** Text for light backgrounds and for dark ones: each theme's own text color. */\nconst INK = tokens.get('--dou-color-text') ?? '#171717'\nconst PAPER = new Map(flattenTokens(dark)).get('--dou-color-text') ?? '#ededed'\n\n/**\n * The text color that reads on a background of a set color, whatever the\n * page's theme: dark on light backgrounds, light on dark ones. `undefined`\n * for a color it can't work out, such as a theme variable, which follows\n * the theme along with the text.\n */\nexport function textOn(background: string): string | undefined {\n if (background.includes('var(')) return undefined\n // a mix of set colors, as color scales paint, is worked out first\n const color = lightThemeColor(background)\n try {\n return contrastRatio(color, INK) >= contrastRatio(color, PAPER) ? INK : PAPER\n } catch {\n return undefined\n }\n}\n\n/** A tone's text color, as `is-tone-*` colors it. */\nconst TONE_TEXT = (tone: string) => `var(--dou-color-${tone}-text)`\n\n/** A tone's soft fill, as `is-fill-*` colors it. */\nconst TONE_FILL: Record<string, string> = {\n neutral: 'color-mix(in oklab, var(--dou-color-text) 6%, var(--dou-color-bg-surface))',\n primary: 'color-mix(in oklab, var(--dou-color-primary) 12%, var(--dou-color-bg-surface))',\n success: 'color-mix(in oklab, var(--dou-color-success) 12%, var(--dou-color-bg-surface))',\n warning: 'color-mix(in oklab, var(--dou-color-warning) 14%, var(--dou-color-bg-surface))',\n danger: 'color-mix(in oklab, var(--dou-color-danger) 12%, var(--dou-color-bg-surface))',\n}\n\n/** The arguments of a CSS function, split at the commas outside brackets. */\nfunction args(text: string) {\n const out: string[] = []\n let depth = 0\n let start = 0\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') depth--\n else if (ch === ',' && depth === 0) {\n out.push(text.slice(start, i).trim())\n start = i + 1\n }\n }\n out.push(text.slice(start).trim())\n return out\n}\n\n/** A color of `color-mix` with its share, such as `red 20%`. */\nfunction mixPart(arg: string): { color: string; amount?: number } {\n const m = /^(.*?)\\s+([\\d.]+)%$/.exec(arg)\n return m ? { color: m[1]!, amount: Number(m[2]) / 100 } : { color: arg }\n}\n\n/**\n * A color as the light theme paints it, as `#rrggbb` where it can be\n * worked out: theme variables and `color-mix(in oklab, …)` of them. Other\n * colors, such as `red`, are given back as they are.\n */\nexport function lightThemeColor(css: string): string {\n const text = css.trim()\n const variable = /^var\\(\\s*(--[\\w-]+)\\s*(?:,\\s*(.+))?\\)$/.exec(text)\n if (variable) {\n const value = tokens.get(variable[1]!) ?? variable[2]\n return value ? lightThemeColor(value) : text\n }\n const mix = /^color-mix\\(\\s*in\\s+oklab\\s*,(.*)\\)$/i.exec(text)\n if (mix) {\n const [first, second] = args(mix[1]!)\n if (!first || !second) return text\n const a = mixPart(first)\n const b = mixPart(second)\n const amount = a.amount ?? (b.amount != null ? 1 - b.amount : 0.5)\n const from = lightThemeColor(a.color)\n const to = lightThemeColor(b.color)\n try {\n return mixColors(from, to, amount)\n } catch {\n return text\n }\n }\n try {\n parseColor(text)\n return text.toLowerCase()\n } catch {\n return text\n }\n}\n\n/**\n * A style with its `tone` and `fill` as the colors they are in the light\n * theme, in `color` and `background`, and every color plain.\n */\nexport function plainStyle(style: SheetStyle): SheetStyle {\n const { tone, fill, ...rest } = style\n const out: SheetStyle = { ...rest }\n const color = rest.color ?? (tone ? TONE_TEXT(tone) : undefined)\n const background = rest.background ?? (fill ? TONE_FILL[fill] : undefined)\n if (color) out.color = lightThemeColor(color)\n if (background) out.background = lightThemeColor(background)\n return out\n}\n"],"mappings":";;AASA,IAAM,SAAS,IAAI,IAAI,cAAc,KAAK,CAAC;;AAG3C,IAAM,MAAM,OAAO,IAAI,kBAAkB,KAAK;AAC9C,IAAM,QAAQ,IAAI,IAAI,cAAc,IAAI,CAAC,CAAC,CAAC,IAAI,kBAAkB,KAAK;;;;;;;AAQtE,SAAgB,OAAO,YAAwC;CAC7D,IAAI,WAAW,SAAS,MAAM,GAAG,OAAO,KAAA;CAExC,MAAM,QAAQ,gBAAgB,UAAU;CACxC,IAAI;EACF,OAAO,cAAc,OAAO,GAAG,KAAK,cAAc,OAAO,KAAK,IAAI,MAAM;CAC1E,QAAQ;EACN;CACF;AACF;;AAGA,IAAM,aAAa,SAAiB,mBAAmB,KAAK;;AAG5D,IAAM,YAAoC;CACxC,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,QAAQ;AACV;;AAGA,SAAS,KAAK,MAAc;CAC1B,MAAM,MAAgB,CAAC;CACvB,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,KAAK,KAAK;EAChB,IAAI,OAAO,KAAK;OACX,IAAI,OAAO,KAAK;OAChB,IAAI,OAAO,OAAO,UAAU,GAAG;GAClC,IAAI,KAAK,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;GACpC,QAAQ,IAAI;EACd;CACF;CACA,IAAI,KAAK,KAAK,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC;CACjC,OAAO;AACT;;AAGA,SAAS,QAAQ,KAAiD;CAChE,MAAM,IAAI,sBAAsB,KAAK,GAAG;CACxC,OAAO,IAAI;EAAE,OAAO,EAAE;EAAK,QAAQ,OAAO,EAAE,EAAE,IAAI;CAAI,IAAI,EAAE,OAAO,IAAI;AACzE;;;;;;AAOA,SAAgB,gBAAgB,KAAqB;CACnD,MAAM,OAAO,IAAI,KAAK;CACtB,MAAM,WAAW,yCAAyC,KAAK,IAAI;CACnE,IAAI,UAAU;EACZ,MAAM,QAAQ,OAAO,IAAI,SAAS,EAAG,KAAK,SAAS;EACnD,OAAO,QAAQ,gBAAgB,KAAK,IAAI;CAC1C;CACA,MAAM,MAAM,wCAAwC,KAAK,IAAI;CAC7D,IAAI,KAAK;EACP,MAAM,CAAC,OAAO,UAAU,KAAK,IAAI,EAAG;EACpC,IAAI,CAAC,SAAS,CAAC,QAAQ,OAAO;EAC9B,MAAM,IAAI,QAAQ,KAAK;EACvB,MAAM,IAAI,QAAQ,MAAM;EACxB,MAAM,SAAS,EAAE,WAAW,EAAE,UAAU,OAAO,IAAI,EAAE,SAAS;EAC9D,MAAM,OAAO,gBAAgB,EAAE,KAAK;EACpC,MAAM,KAAK,gBAAgB,EAAE,KAAK;EAClC,IAAI;GACF,OAAO,UAAU,MAAM,IAAI,MAAM;EACnC,QAAQ;GACN,OAAO;EACT;CACF;CACA,IAAI;EACF,WAAW,IAAI;EACf,OAAO,KAAK,YAAY;CAC1B,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAgB,WAAW,OAA+B;CACxD,MAAM,EAAE,MAAM,MAAM,GAAG,SAAS;CAChC,MAAM,MAAkB,EAAE,GAAG,KAAK;CAClC,MAAM,QAAQ,KAAK,UAAU,OAAO,UAAU,IAAI,IAAI,KAAA;CACtD,MAAM,aAAa,KAAK,eAAe,OAAO,UAAU,QAAQ,KAAA;CAChE,IAAI,OAAO,IAAI,QAAQ,gBAAgB,KAAK;CAC5C,IAAI,YAAY,IAAI,aAAa,gBAAgB,UAAU;CAC3D,OAAO;AACT"}
@@ -0,0 +1,54 @@
1
+ import { formatValue, parseInput } from "./format.js";
2
+ import { defineSheetCellType, optionOf } from "./cell-type.js";
3
+ // src/core/plain-cell-types.ts
4
+ var DAY = /^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}(?::\d{2})?)?$/;
5
+ /** `11/30`, `11-30`, `11月30日`, `2026年11月30日`: a day without the year is in this one. */
6
+ var SHORT_DAY = /^(?:(\d{4})年)?(\d{1,2})(?:[/-]|月)(\d{1,2})日?$/;
7
+ function shortDay(text) {
8
+ const match = SHORT_DAY.exec(text.trim());
9
+ if (!match) return void 0;
10
+ const [, y, m, d] = match;
11
+ const iso = `${y ?? String((/* @__PURE__ */ new Date()).getFullYear())}-${m.padStart(2, "0")}-${d.padStart(2, "0")}`;
12
+ const day = /* @__PURE__ */ new Date(`${iso}T00:00:00Z`);
13
+ return !Number.isNaN(day.getTime()) && day.toISOString().startsWith(iso) ? iso : void 0;
14
+ }
15
+ /**
16
+ * The built-in types without what they draw or edit with, so files can be
17
+ * read and written where there is no page, such as in a Worker.
18
+ */
19
+ var plainCellTypes = [
20
+ defineSheetCellType({
21
+ name: "date",
22
+ parse(text) {
23
+ const day = shortDay(text);
24
+ return day ? { value: day } : void 0;
25
+ },
26
+ valid: (value, _, m) => typeof value === "string" && DAY.test(value) || m.notDate
27
+ }),
28
+ defineSheetCellType({
29
+ name: "select",
30
+ parse(text, setup) {
31
+ const query = text.trim().toLowerCase();
32
+ if (!query) return void 0;
33
+ for (const option of setup.options ?? []) {
34
+ const { value } = optionOf(option);
35
+ if (formatValue(value).toLowerCase() === query) return { value };
36
+ }
37
+ },
38
+ valid: (value, setup, m) => (setup.options ?? []).some((option) => optionOf(option).value === value) || m.notOption
39
+ }),
40
+ defineSheetCellType({
41
+ name: "checkbox",
42
+ parse(text) {
43
+ const value = parseInput(text).value;
44
+ if (value === 1 || value === 0) return { value: value === 1 };
45
+ return typeof value === "boolean" || value === null ? { value } : void 0;
46
+ },
47
+ valid: (value, _, m) => typeof value === "boolean" || m.notChecked,
48
+ toggle: (value) => value !== true,
49
+ align: "center"
50
+ })
51
+ ];
52
+ export { plainCellTypes };
53
+
54
+ //# sourceMappingURL=plain-cell-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain-cell-types.js","names":[],"sources":["../../src/core/plain-cell-types.ts"],"sourcesContent":["import { defineSheetCellType, optionOf, type SheetCellType } from './cell-type'\nimport { formatValue, parseInput } from './format'\n\nconst DAY = /^\\d{4}-\\d{2}-\\d{2}(?: \\d{2}:\\d{2}(?::\\d{2})?)?$/\n/** `11/30`, `11-30`, `11月30日`, `2026年11月30日`: a day without the year is in this one. */\nconst SHORT_DAY = /^(?:(\\d{4})年)?(\\d{1,2})(?:[/-]|月)(\\d{1,2})日?$/\n\nfunction shortDay(text: string) {\n const match = SHORT_DAY.exec(text.trim())\n if (!match) return undefined\n const [, y, m, d] = match\n const year = y ?? String(new Date().getFullYear())\n const iso = `${year}-${m!.padStart(2, '0')}-${d!.padStart(2, '0')}`\n const day = new Date(`${iso}T00:00:00Z`)\n return !Number.isNaN(day.getTime()) && day.toISOString().startsWith(iso) ? iso : undefined\n}\n\n/** A day: typed in (in the common ways) or picked on a calendar under the cell. */\nconst date = defineSheetCellType({\n name: 'date',\n parse(text) {\n const day = shortDay(text)\n return day ? { value: day } : undefined\n },\n valid: (value, _, m) => (typeof value === 'string' && DAY.test(value)) || m.notDate,\n})\n\n/** One of the `options` of its setup, picked from a list under the cell. */\nconst select = defineSheetCellType({\n name: 'select',\n // typed text that names an option, in any case, is that option\n parse(text, setup) {\n const query = text.trim().toLowerCase()\n if (!query) return undefined\n for (const option of setup.options ?? []) {\n const { value } = optionOf(option)\n if (formatValue(value).toLowerCase() === query) return { value }\n }\n return undefined\n },\n valid: (value, setup, m) =>\n (setup.options ?? []).some((option) => optionOf(option).value === value) || m.notOption,\n})\n\n/** `true` or `false`, shown as a box that a click or `Space` checks. */\nconst checkbox = defineSheetCellType({\n name: 'checkbox',\n parse(text) {\n const value = parseInput(text).value\n if (value === 1 || value === 0) return { value: value === 1 }\n return typeof value === 'boolean' || value === null ? { value } : undefined\n },\n valid: (value, _, m) => typeof value === 'boolean' || m.notChecked,\n toggle: (value) => value !== true,\n align: 'center',\n})\n\n/**\n * The built-in types without what they draw or edit with, so files can be\n * read and written where there is no page, such as in a Worker.\n */\nexport const plainCellTypes: readonly SheetCellType[] = [date, select, checkbox]\n"],"mappings":";;;AAGA,IAAM,MAAM;;AAEZ,IAAM,YAAY;AAElB,SAAS,SAAS,MAAc;CAC9B,MAAM,QAAQ,UAAU,KAAK,KAAK,KAAK,CAAC;CACxC,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,MAAM,GAAG,GAAG,GAAG,KAAK;CAEpB,MAAM,MAAM,GADC,KAAK,wBAAO,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,EAC7B,GAAG,EAAG,SAAS,GAAG,GAAG,EAAE,GAAG,EAAG,SAAS,GAAG,GAAG;CAChE,MAAM,sBAAM,IAAI,KAAK,GAAG,IAAI,WAAW;CACvC,OAAO,CAAC,OAAO,MAAM,IAAI,QAAQ,CAAC,KAAK,IAAI,YAAY,CAAC,CAAC,WAAW,GAAG,IAAI,MAAM,KAAA;AACnF;;;;;AA8CA,IAAa,iBAA2C;CA3C3C,oBAAoB;EAC/B,MAAM;EACN,MAAM,MAAM;GACV,MAAM,MAAM,SAAS,IAAI;GACzB,OAAO,MAAM,EAAE,OAAO,IAAI,IAAI,KAAA;EAChC;EACA,QAAQ,OAAO,GAAG,MAAO,OAAO,UAAU,YAAY,IAAI,KAAK,KAAK,KAAM,EAAE;CAC9E,CAoCyD;CAjC1C,oBAAoB;EACjC,MAAM;EAEN,MAAM,MAAM,OAAO;GACjB,MAAM,QAAQ,KAAK,KAAK,CAAC,CAAC,YAAY;GACtC,IAAI,CAAC,OAAO,OAAO,KAAA;GACnB,KAAK,MAAM,UAAU,MAAM,WAAW,CAAC,GAAG;IACxC,MAAM,EAAE,UAAU,SAAS,MAAM;IACjC,IAAI,YAAY,KAAK,CAAC,CAAC,YAAY,MAAM,OAAO,OAAO,EAAE,MAAM;GACjE;EAEF;EACA,QAAQ,OAAO,OAAO,OACnB,MAAM,WAAW,CAAC,EAAA,CAAG,MAAM,WAAW,SAAS,MAAM,CAAC,CAAC,UAAU,KAAK,KAAK,EAAE;CAClF,CAmB+D;CAhB9C,oBAAoB;EACnC,MAAM;EACN,MAAM,MAAM;GACV,MAAM,QAAQ,WAAW,IAAI,CAAC,CAAC;GAC/B,IAAI,UAAU,KAAK,UAAU,GAAG,OAAO,EAAE,OAAO,UAAU,EAAE;GAC5D,OAAO,OAAO,UAAU,aAAa,UAAU,OAAO,EAAE,MAAM,IAAI,KAAA;EACpE;EACA,QAAQ,OAAO,GAAG,MAAM,OAAO,UAAU,aAAa,EAAE;EACxD,SAAS,UAAU,UAAU;EAC7B,OAAO;CACT,CAMuE;AAAQ"}