js-spread-grid 0.2.7 → 1.1.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 (97) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +2 -1
  3. package/src/core/cursor.js +20 -0
  4. package/src/core/error.js +30 -0
  5. package/src/core/input.js +42 -0
  6. package/src/core/render.js +25 -159
  7. package/src/core/scroll.js +219 -0
  8. package/src/core/state.js +176 -71
  9. package/src/core/style.js +26 -0
  10. package/src/core/tooltip.js +33 -0
  11. package/src/core-utils/rect.js +14 -12
  12. package/src/core-utils/rect.test.js +7 -7
  13. package/src/core-utils/stringifyId.js +4 -2
  14. package/src/index.d.ts +6 -0
  15. package/src/index.js +200 -158
  16. package/src/state-utils/getActive.js +7 -6
  17. package/src/state-utils/getBoundingClientSize.js +14 -0
  18. package/src/state-utils/getCellEditType.js +5 -3
  19. package/src/state-utils/getCellPlacement.js +8 -6
  20. package/src/state-utils/getCellSection.js +9 -7
  21. package/src/state-utils/getClientSize.js +12 -0
  22. package/src/state-utils/getClipboardData.js +6 -4
  23. package/src/state-utils/getColumnIndex.js +3 -1
  24. package/src/state-utils/getCombinedCells.js +3 -1
  25. package/src/state-utils/getContextFormatting.js +4 -2
  26. package/src/state-utils/getCursorState.js +11 -0
  27. package/src/state-utils/getDataFormatting.js +11 -9
  28. package/src/state-utils/getDynamicRowHeight.js +10 -0
  29. package/src/state-utils/getEditableCells.js +7 -5
  30. package/src/state-utils/getEditedCellsAndFilters.js +5 -3
  31. package/src/state-utils/getEdition.js +3 -1
  32. package/src/state-utils/getFilterFormatting.js +4 -2
  33. package/src/state-utils/getFiltered.js +39 -19
  34. package/src/state-utils/getFilteringRules.js +3 -1
  35. package/src/state-utils/getFixedSize.js +91 -6
  36. package/src/state-utils/getFormatResolver.js +6 -4
  37. package/src/state-utils/getFormattingRules.js +3 -1
  38. package/src/state-utils/getGenerated.js +8 -6
  39. package/src/state-utils/getHighlightedCells.js +9 -7
  40. package/src/state-utils/getHoveredCell.js +13 -14
  41. package/src/state-utils/getInputFormatting.js +4 -2
  42. package/src/state-utils/getInputPlacement.js +8 -6
  43. package/src/state-utils/getInputState.js +10 -0
  44. package/src/state-utils/getInternalPosition.js +12 -16
  45. package/src/state-utils/getIsTextValid.js +3 -1
  46. package/src/state-utils/getKeys.js +12 -3
  47. package/src/state-utils/getLookup.js +4 -2
  48. package/src/state-utils/getMeasureFormatting.js +4 -2
  49. package/src/state-utils/getMeasured.js +36 -20
  50. package/src/state-utils/getMousePosition.js +3 -1
  51. package/src/state-utils/getNewSortBy.js +14 -9
  52. package/src/state-utils/getOrder.js +4 -2
  53. package/src/state-utils/getOrdered.js +62 -25
  54. package/src/state-utils/getPinned.js +17 -3
  55. package/src/state-utils/getPinning.js +43 -0
  56. package/src/state-utils/getPlaced.js +17 -14
  57. package/src/state-utils/getReducedCells.js +4 -2
  58. package/src/state-utils/getReducedFormatting.js +11 -9
  59. package/src/state-utils/getRenderFormatting.js +26 -16
  60. package/src/state-utils/getRenderState.js +14 -0
  61. package/src/state-utils/getReordered.js +13 -11
  62. package/src/state-utils/getResizable.js +24 -26
  63. package/src/state-utils/getResolved.js +115 -52
  64. package/src/state-utils/getResolvedFilters.js +4 -2
  65. package/src/state-utils/getResolvedScrollSpeed.js +37 -0
  66. package/src/state-utils/getResolvedSortBy.js +4 -2
  67. package/src/state-utils/getRowIndex.js +3 -1
  68. package/src/state-utils/getScrollOffset.js +12 -0
  69. package/src/state-utils/getScrollRect.js +12 -18
  70. package/src/state-utils/getScrollState.js +19 -0
  71. package/src/state-utils/getScrollTarget.js +136 -0
  72. package/src/state-utils/getSections.js +6 -4
  73. package/src/state-utils/getSelection.js +3 -1
  74. package/src/state-utils/getSortOrderFormatting.js +11 -0
  75. package/src/state-utils/getSorted.js +44 -30
  76. package/src/state-utils/getSortingFormatting.js +4 -2
  77. package/src/state-utils/getSortingRules.js +3 -1
  78. package/src/state-utils/getStatic.js +251 -0
  79. package/src/state-utils/getStatic.test.js +455 -0
  80. package/src/state-utils/getStyleState.js +11 -0
  81. package/src/state-utils/getToggledValue.js +6 -4
  82. package/src/state-utils/getTooltip.js +6 -4
  83. package/src/state-utils/getTooltipPlacement.js +4 -2
  84. package/src/state-utils/getTooltipState.js +10 -0
  85. package/src/state-utils/getTotalSize.js +47 -6
  86. package/src/state-utils/getUnfolded.js +23 -21
  87. package/src/state-utils/getWithAssumedId.js +5 -3
  88. package/src/types/Edition.js +14 -12
  89. package/src/types/FilteringRules.js +11 -9
  90. package/src/types/FormatResolver.js +8 -6
  91. package/src/types/FormattingRules.js +28 -19
  92. package/src/types/FormattingRules.test.js +6 -5
  93. package/src/types/RulesLookup.js +12 -10
  94. package/src/types/Selection.js +8 -6
  95. package/src/types/SortingRules.js +37 -21
  96. package/src/types/TextResolver.js +4 -2
  97. package/src/typings.js +351 -35
@@ -0,0 +1,219 @@
1
+ /** @import * as Types from "../typings.js"; */
2
+
3
+ /**
4
+ * @param {Types.ScrollSpeedStep[]} speed
5
+ * @param {number} distance
6
+ * @returns {Types.ScrollSpeed}
7
+ */
8
+ function getSpeedValue(speed, distance) {
9
+ const absDistance = Math.abs(distance);
10
+ for (const entry of speed) {
11
+ if (absDistance <= entry.maxDistance)
12
+ return entry.scrollSpeed;
13
+ }
14
+
15
+ return 'auto';
16
+ }
17
+
18
+
19
+ /**
20
+ * @param {Types.Context} context
21
+ */
22
+ function cancelScrollAnimation(context) {
23
+ const element = context.element;
24
+
25
+ if (context.scrollAnimationId) {
26
+ cancelAnimationFrame(context.scrollAnimationId);
27
+ context.scrollAnimationId = null;
28
+ element.scrollTop = element.scrollTop;
29
+ element.scrollLeft = element.scrollLeft;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @param {number} diff
35
+ * @param {number} speed
36
+ * @param {number} dt
37
+ * @param {number} fractionalProgress
38
+ * @returns {number}
39
+ */
40
+ function getStep(diff, speed, dt, fractionalProgress) {
41
+ return diff > 0
42
+ ? Math.min(diff, speed * dt + fractionalProgress)
43
+ : Math.max(diff, -speed * dt + fractionalProgress);
44
+ }
45
+
46
+ /**
47
+ * @param {number} target
48
+ * @param {number} clientSize
49
+ * @param {number} scrollSize
50
+ * @returns {number}
51
+ */
52
+ function clipTarget(target, clientSize, scrollSize) {
53
+ return Math.max(0, Math.min(target, scrollSize - clientSize - 1));
54
+ }
55
+
56
+ /**
57
+ * @param {'top' | 'left'} axis
58
+ * @param {number} dt
59
+ * @param {number} currentPosition
60
+ * @param {number} target
61
+ * @param {number} clientSize
62
+ * @param {number} scrollSize
63
+ * @param {number} previousTarget
64
+ * @param {number} previousOffset
65
+ * @param {Types.ScrollSpeedStep[]} speedSteps
66
+ * @param {number} fractionalProgress
67
+ * @param {boolean} snap
68
+ * @returns {ScrollToOptions & {behavior?: 'auto'} | null}
69
+ */
70
+ function getOptions(axis, dt, currentPosition, target, clientSize, scrollSize, previousTarget, previousOffset, speedSteps, fractionalProgress, snap) {
71
+ if (target === null)
72
+ return null;
73
+
74
+ const diff = target - currentPosition;
75
+
76
+ if (Math.abs(diff) < 1)
77
+ return null;
78
+
79
+ if (diff < 0 && currentPosition === 0)
80
+ return null;
81
+
82
+ if (diff > 0 && currentPosition >= scrollSize - clientSize - 1)
83
+ return null;
84
+
85
+ const speed = getSpeedValue(speedSteps, diff);
86
+
87
+ if (snap || speed === 'auto')
88
+ return { [axis]: target, behavior: 'auto' };
89
+
90
+ if (typeof speed === 'number')
91
+ return { [axis]: currentPosition + getStep(diff, speed, dt, fractionalProgress), behavior: 'auto' };
92
+
93
+ if (previousTarget !== target || previousOffset === currentPosition)
94
+ return { [axis]: target };
95
+
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * @param {Types.Context} context
101
+ * @param {number} dt
102
+ * @param {ScrollToOptions} previousTarget
103
+ * @param {Types.ScrollOffset} previousOffset
104
+ * @param {number} fractionalProgress
105
+ * @param {boolean} snap
106
+ * @returns {ScrollToOptions & {behavior?: 'auto'}}
107
+ */
108
+ function getVerticalOptions(context, dt, previousTarget, previousOffset, fractionalProgress, snap) {
109
+ const element = context.element;
110
+ const scrollState = context.state.scrollState;
111
+ const target = scrollState.verticalTarget;
112
+ const currentPosition = element.scrollTop;
113
+ const clientSize = element.clientHeight;
114
+ const scrollSize = element.scrollHeight;
115
+
116
+ return getOptions('top', dt, currentPosition, target, clientSize, scrollSize, previousTarget?.top, previousOffset.top, scrollState.verticalSpeed, fractionalProgress, snap);
117
+ }
118
+
119
+ /**
120
+ * @param {Types.Context} context
121
+ * @param {number} dt
122
+ * @param {ScrollToOptions} previousTarget
123
+ * @param {Types.ScrollOffset} previousOffset
124
+ * @param {number} fractionalProgress
125
+ * @param {boolean} snap
126
+ * @returns {ScrollToOptions & {behavior?: 'auto'}}
127
+ */
128
+ function getHorizontalOptions(context, dt, previousTarget, previousOffset, fractionalProgress, snap) {
129
+ const element = context.element;
130
+ const scrollState = context.state.scrollState;
131
+ const target = scrollState.horizontalTarget;
132
+ const currentPosition = element.scrollLeft;
133
+ const clientSize = element.clientWidth;
134
+ const scrollSize = element.scrollWidth;
135
+
136
+ return getOptions('left', dt, currentPosition, target, clientSize, scrollSize, previousTarget?.left, previousOffset.left, scrollState.horizontalSpeed, fractionalProgress, snap);
137
+ }
138
+
139
+ /**
140
+ * @param {Types.Context} context
141
+ */
142
+ export default function scroll(context) {
143
+ const element = context.element;
144
+ const scrollState = context.state.scrollState;
145
+ const targetV = scrollState.verticalTarget;
146
+ const targetH = scrollState.horizontalTarget;
147
+
148
+ if (targetV === null && targetH === null || !element.isConnected) {
149
+ cancelScrollAnimation(context);
150
+ return;
151
+ }
152
+
153
+ if (context.scrollAnimationId) {
154
+ return;
155
+ }
156
+
157
+ let lastTime = performance.now();
158
+ let previousOptionsH = /** @type {ScrollToOptions|null} */ (null);
159
+ let previousOptionsV = /** @type {ScrollToOptions|null} */ (null);
160
+ let previousOffset = { top: element.scrollTop, left: element.scrollLeft };
161
+ let fractionalVerticalProgress = 0;
162
+ let fractionalHorizontalProgress = 0;
163
+
164
+ /** @param {number} now */
165
+ const animate = (now) => {
166
+ const targetV = context.state.scrollState.verticalTarget;
167
+ const targetH = context.state.scrollState.horizontalTarget;
168
+
169
+ if (targetV === null && targetH === null || !element.isConnected) {
170
+ cancelScrollAnimation(context);
171
+ return;
172
+ }
173
+
174
+ if (targetV === null)
175
+ previousOptionsV = null;
176
+ if (targetH === null)
177
+ previousOptionsH = null;
178
+
179
+ const dt = (now - lastTime) / 1000;
180
+ lastTime = now;
181
+
182
+ const snap = context.lastScrollClientSizeVersion !== context.state.scrollState.clientSizeVersion;
183
+ context.lastScrollClientSizeVersion = context.state.scrollState.clientSizeVersion;
184
+
185
+ const newOptionsV = getVerticalOptions(context, dt, previousOptionsV, previousOffset, fractionalVerticalProgress, snap);
186
+ const newOptionsH = getHorizontalOptions(context, dt, previousOptionsH, previousOffset, fractionalHorizontalProgress, snap);
187
+ const newOptions = {
188
+ ...previousOptionsV,
189
+ ...previousOptionsH,
190
+ behavior: /** @type {Types.ScrollBehavior} */('smooth'),
191
+ ...newOptionsV,
192
+ ...newOptionsH
193
+ };
194
+
195
+ if (newOptionsV || newOptionsH) {
196
+ element.scrollTo(newOptions);
197
+ }
198
+
199
+ if (newOptionsV && newOptions.behavior === 'auto') {
200
+ fractionalVerticalProgress = newOptions.top - element.scrollTop;
201
+ } else {
202
+ fractionalVerticalProgress = 0;
203
+ }
204
+
205
+ if (newOptionsH && newOptions.behavior === 'auto') {
206
+ fractionalHorizontalProgress = newOptions.left - element.scrollLeft;
207
+ } else {
208
+ fractionalHorizontalProgress = 0;
209
+ }
210
+
211
+ previousOptionsV = newOptionsV || previousOptionsV;
212
+ previousOptionsH = newOptionsH || previousOptionsH;
213
+ previousOffset = { top: element.scrollTop, left: element.scrollLeft };
214
+
215
+ context.scrollAnimationId = requestAnimationFrame(animate);
216
+ };
217
+
218
+ animate(lastTime);
219
+ }
package/src/core/state.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** @import * as Types from "../typings.js"; */
2
+
1
3
  import getEditableCells from "../state-utils/getEditableCells.js";
2
4
  import getDataFormatting from "../state-utils/getDataFormatting.js";
3
5
  import getInputFormatting from "../state-utils/getInputFormatting.js";
@@ -28,6 +30,7 @@ import getMeasureFormatting from "../state-utils/getMeasureFormatting.js";
28
30
  import { getMeasuredColumns, getMeasuredRows } from "../state-utils/getMeasured.js";
29
31
  import getKeys from "../state-utils/getKeys.js";
30
32
  import getSortingFormatting from "../state-utils/getSortingFormatting.js";
33
+ import getSortOrderFormatting from "../state-utils/getSortOrderFormatting.js";
31
34
  import getSortingRules from "../state-utils/getSortingRules.js";
32
35
  import { getSortedColumns, getSortedRows } from "../state-utils/getSorted.js";
33
36
  import { getResizableColumn, getResizableRow } from "../state-utils/getResizable.js";
@@ -38,13 +41,107 @@ import getContextFormatting from "../state-utils/getContextFormatting.js";
38
41
  import getTooltip from "../state-utils/getTooltip.js";
39
42
  import getTooltipPlacement from "../state-utils/getTooltipPlacement.js";
40
43
  import getOrder from "../state-utils/getOrder.js";
41
- import getOrdered from "../state-utils/getOrdered.js";
44
+ import { getOrderedColumns, getOrderedRows } from "../state-utils/getOrdered.js";
45
+ import { getStaticColumns, getStaticRows } from "../state-utils/getStatic.js";
46
+ import getPinning from "../state-utils/getPinning.js";
47
+ import getClientSize from "../state-utils/getClientSize.js";
48
+ import getScrollOffset from "../state-utils/getScrollOffset.js";
49
+ import getInternalPosition from "../state-utils/getInternalPosition.js";
50
+ import stringifyId from "../core-utils/stringifyId.js";
51
+ import { getHorizontalScrollTarget, getVerticalScrollTarget } from "../state-utils/getScrollTarget.js";
52
+ import getResolvedScrollSpeed from "../state-utils/getResolvedScrollSpeed.js";
53
+ import getScrollState from "../state-utils/getScrollState.js";
54
+ import getStyleState from "../state-utils/getStyleState.js";
55
+ import getRenderState from "../state-utils/getRenderState.js";
56
+ import getInputState from "../state-utils/getInputState.js";
57
+ import getCursorState from "../state-utils/getCursorState.js";
58
+ import getTooltipState from "../state-utils/getTooltipState.js";
59
+ import getDynamicRowHeight from "../state-utils/getDynamicRowHeight.js";
60
+
61
+ /**
62
+ * @param {Types.Position} a
63
+ * @param {Types.Position} b
64
+ * @returns {boolean}
65
+ */
66
+ function comparePositions(a, b) {
67
+ if (!a && !b)
68
+ return true;
69
+ if (!a || !b)
70
+ return false;
71
+ return a.x === b.x && a.y === b.y;
72
+ }
73
+
74
+ /**
75
+ * @param {Types.ClientSize} a
76
+ * @param {Types.ClientSize} b
77
+ * @returns {boolean}
78
+ */
79
+ function compareClientSizes(a, b) {
80
+ if (!a && !b)
81
+ return true;
82
+ if (!a || !b)
83
+ return false;
84
+ return a.width === b.width && a.height === b.height;
85
+ }
86
+
87
+ /**
88
+ * @param {Types.ScrollOffset} a
89
+ * @param {Types.ScrollOffset} b
90
+ * @returns {boolean}
91
+ */
92
+ function compareScrollOffsets(a, b) {
93
+ if (!a && !b)
94
+ return true;
95
+ if (!a || !b)
96
+ return false;
97
+ return a.left === b.left && a.top === b.top;
98
+ }
99
+
100
+ /**
101
+ * @param {Types.Rect} a
102
+ * @param {Types.Rect} b
103
+ * @returns {boolean}
104
+ */
105
+ function compareRects(a, b) {
106
+ if (!a && !b)
107
+ return true;
108
+ if (!a || !b)
109
+ return false;
110
+ return a.left === b.left && a.top === b.top && a.width === b.width && a.height === b.height;
111
+ }
112
+
113
+ /**
114
+ * @param {Types.CellId} a
115
+ * @param {Types.CellId} b
116
+ * @returns {boolean}
117
+ */
118
+ function compareCellIds(a, b) {
119
+ if (!a && !b)
120
+ return true;
121
+ if (!a || !b)
122
+ return false;
123
+ return stringifyId(a.rowId) === stringifyId(b.rowId) && stringifyId(a.columnId) === stringifyId(b.columnId);
124
+ }
125
+
126
+ /**
127
+ * @param {Types.StyleState} a
128
+ * @param {Types.StyleState} b
129
+ * @returns {boolean}
130
+ */
131
+ function compareStyleStates(a, b) {
132
+ if (!a && !b)
133
+ return true;
134
+ if (!a || !b)
135
+ return false;
136
+ return a.scrollbarWidth === b.scrollbarWidth;
137
+ }
138
+
42
139
 
43
140
  // TODO: write some test to check if the cache is working properly
44
141
  /**
45
- * @param {Context} context
142
+ * @param {Types.Context} context
46
143
  */
47
- function updateStateInternal(context) {
144
+ export default function updateState(context) {
48
145
  // console.count('updateState');
49
146
 
50
147
  const options = { ...context.localOptions, ...context.externalOptions };
@@ -57,53 +154,46 @@ function updateStateInternal(context) {
57
154
 
58
155
  // TODO: Move to utils
59
156
  /**
60
- * @template {keyof Memory} K
157
+ * @template {keyof Types.Memory} K
61
158
  * @template {Array<*>} U
62
159
  * @param {K} key
63
- * @param {(...args: U) => Memory[K]['value']} func
160
+ * @param {(...args: U) => Types.Memory[K]['value']} func
64
161
  * @param {U} dependencies
65
- * @returns {Memory[K]['value']}
162
+ * @param {(a: Types.Memory[K]['value'], b: Types.Memory[K]['value']) => boolean} [comparator]
163
+ * @returns {Types.Memory[K]['value']}
66
164
  */
67
- function cache(key, func, dependencies) {
165
+ function cache(key, func, dependencies, comparator = (a, b) => a === b) {
68
166
  const previousDependencies = memory[key] && memory[key].dependencies;
69
167
  if (!previousDependencies || dependencies.some((dependency, index) => dependency !== previousDependencies[index])) {
70
- memory[key] = /** @type {Memory[K]} */ ({ value: func(...dependencies), dependencies });
71
- stateChanges.push(key);
72
- }
73
- return memory[key].value;
74
- }
75
-
76
- /**
77
- * @template {keyof Memory} K
78
- * @param {K} key
79
- * @param {Memory[K]['value']} value
80
- * @returns {Memory[K]['value']}
81
- */
82
- function cache_value_deep(key, value) {
83
- const previousValue = memory[key] && memory[key].value;
84
- if (JSON.stringify(previousValue) !== JSON.stringify(value)) {
85
- memory[key] = /** @type {Memory[K]} */ ({ value });
86
- stateChanges.push(key);
168
+ const previousValue = memory[key] && memory[key].value;
169
+ const previousVersion = memory[key] && memory[key].version || 0;
170
+ const newValue = func(...dependencies);
171
+ if (!comparator(previousValue, newValue)) {
172
+ memory[key] = /** @type {Types.Memory[K]} */ ({ value: newValue, dependencies, version: previousVersion + 1 });
173
+ stateChanges.push(key);
174
+ } else {
175
+ memory[key] = /** @type {Types.Memory[K]} */ ({ value: previousValue, dependencies, version: previousVersion });
176
+ }
87
177
  }
88
178
  return memory[key].value;
89
179
  }
90
180
 
91
181
  /**
92
- * @template {keyof Memory} K
182
+ * @template {keyof Types.Memory} K
93
183
  * @param {K} key
94
- * @param {Memory[K]['value']} value
95
- * @param {(value: Memory[K]['value']) => void} callback
184
+ * @param {Types.Memory[K]['value']} value
185
+ * @param {(value: Types.Memory[K]['value']) => void} callback
96
186
  */
97
187
  function invoke_change_callback(key, value, callback) {
98
188
  const previousValue = memory[key] && memory[key].value;
99
189
  if (previousValue !== value) {
100
- memory[key] = /** @type {Memory[K]} */ ({ value });
190
+ memory[key] = /** @type {Types.Memory[K]} */ ({ value });
101
191
  callback(value);
102
192
  }
103
193
  }
104
194
 
105
- const devicePixelRatio = window.devicePixelRatio; // TODO: Trigger update on devicePixelRatio change
106
- const borderWidth = options.borderWidth / devicePixelRatio;
195
+ const devicePixelRatio = /** @type {number} */ cache('devicePixelRatio', v => v, [window.devicePixelRatio]);
196
+ const borderWidth = /** @type {number} */ cache('borderWidth', v => v, [options.borderWidth / devicePixelRatio]);
107
197
  const data = options.data;
108
198
  const text = context.input.value;
109
199
  const sortBy = cache('sortBy', getResolvedSortBy, [options.sortBy]);
@@ -114,22 +204,25 @@ function updateStateInternal(context) {
114
204
  const edition = cache('edition', getEdition, [editedCellsAndFilters]);
115
205
  const invokedColumns = cache('invokedColumns', getGeneratedColumns, [options.columns, data]);
116
206
  const invokedRows = cache('invokedRows', getGeneratedRows, [options.rows, data]);
207
+ const mousePosition = cache('mousePosition', v => v, [context.mousePosition], comparePositions);
117
208
  // TODO: throw on duplicate ids
118
209
  // TODO: throw on duplicate row/column filter ids
119
210
 
120
211
  // Resolving
121
212
  const unfoldedColumns = cache('unfoldedColumns', getUnfoldedColumns, [invokedColumns, data]);
122
213
  const unfoldedRows = cache('unfoldedRows', getUnfoldedRows, [invokedRows, data]);
123
- const unfilteredColumns = cache('unfilteredColumns', getResolvedColumns, [unfoldedColumns, options.pinnedLeft, options.pinnedRight, options.columnWidths]);
124
- const unfilteredRows = cache('unfilteredRows', getResolvedRows, [unfoldedRows, options.pinnedTop, options.pinnedBottom, options.rowHeights]);
214
+ const dynamicColumnWidth = 150;
215
+ const dynamicRowHeight = cache('dynamicRowHeight', getDynamicRowHeight, [textResolver]);
216
+ const unfilteredColumns = cache('unfilteredColumns', getResolvedColumns, [unfoldedColumns, options.columnWidths, borderWidth, devicePixelRatio, dynamicColumnWidth]);
217
+ const unfilteredRows = cache('unfilteredRows', getResolvedRows, [unfoldedRows, options.rowHeights, borderWidth, devicePixelRatio, dynamicRowHeight]);
125
218
  const unfilteredColumnKeys = cache('unfilteredColumnKeys', getKeys, [unfilteredColumns]);
126
219
  const unfilteredRowKeys = cache('unfilteredRowKeys', getKeys, [unfilteredRows]);
127
220
 
128
221
  // Ordering
129
222
  const columnsOrder = cache('columnsOrder', getOrder, [options.columnsOrder]);
130
223
  const rowsOrder = cache('rowsOrder', getOrder, [options.rowsOrder]);
131
- const orderedColumns = cache('orderedColumns', getOrdered, [unfilteredColumns, columnsOrder]);
132
- const orderedRows = cache('orderedRows', getOrdered, [unfilteredRows, rowsOrder]);
224
+ const orderedColumns = cache('orderedColumns', getOrderedColumns, [unfilteredColumns, columnsOrder]);
225
+ const orderedRows = cache('orderedRows', getOrderedRows, [unfilteredRows, rowsOrder]);
133
226
 
134
227
  // Filtering
135
228
  const filterFormatting = cache('filterFormatting', getFilterFormatting, [dataFormatting]);
@@ -145,9 +238,10 @@ function updateStateInternal(context) {
145
238
  const sortedColumns = cache('sortedColumns', getSortedColumns, [sortBy, sortingRules, sortingFormattingRules, data, filteredRows, filteredColumns, edition]);
146
239
  const sortedRows = cache('sortedRows', getSortedRows, [sortBy, sortingRules, sortingFormattingRules, data, filteredRows, filteredColumns, edition]);
147
240
 
148
- // Shaping
241
+ // Shaping finalization
149
242
  const shapedColumns = sortedColumns;
150
243
  const shapedRows = sortedRows;
244
+ const pinning = cache('pinning', getPinning, [shapedColumns, shapedRows, options.pinnedTop, options.pinnedBottom, options.pinnedLeft, options.pinnedRight]);
151
245
 
152
246
  // Measuring
153
247
  const measureFormatting = cache('measureFormatting', getMeasureFormatting, [dataFormatting]);
@@ -155,24 +249,39 @@ function updateStateInternal(context) {
155
249
  const measureFormatResolver = cache('measureFormatResolver', getFormatResolver, [measureFormattingRules, data, shapedRows, shapedColumns, edition]);
156
250
  const columnWidthCache = context.columnWidthCache;
157
251
  const rowHeightCache = context.rowHeightCache;
158
- const measuredColumns = cache('measuredColumns', getMeasuredColumns, [shapedColumns, shapedRows, textResolver, measureFormatResolver, columnWidthCache, unfilteredColumnKeys]);
159
- const measuredRows = cache('measuredRows', getMeasuredRows, [shapedColumns, shapedRows, textResolver, measureFormatResolver, rowHeightCache, unfilteredRowKeys]);
252
+ const measuredColumns = cache('measuredColumns', getMeasuredColumns, [shapedColumns, shapedRows, textResolver, measureFormatResolver, columnWidthCache, unfilteredColumnKeys, devicePixelRatio]);
253
+ const measuredRows = cache('measuredRows', getMeasuredRows, [shapedColumns, shapedRows, textResolver, measureFormatResolver, rowHeightCache, unfilteredRowKeys, devicePixelRatio]);
254
+
255
+ // Scrolling
256
+ const fixedSize = cache('fixedSize', getFixedSize, [measuredColumns, measuredRows, pinning, borderWidth]);
257
+ const totalSize = cache('totalSize', getTotalSize, [measuredColumns, measuredRows, borderWidth]);
258
+ const clientSize = cache('clientSize', v => v, [getClientSize(element)], compareClientSizes);
259
+ const boundingClientSize = cache('boundingClientSize', v => v, [getClientSize(element)], compareClientSizes);
260
+ const scrollOffset = cache('scrollOffset', v => v, [getScrollOffset(element)], compareScrollOffsets);
261
+ const internalMousePosition = cache('internalMousePosition', getInternalPosition, [mousePosition, clientSize, scrollOffset, fixedSize, totalSize], comparePositions);
262
+ const scrollRect = cache('scrollRect', getScrollRect, [previousState?.renderState?.scrollRect, scrollOffset, totalSize, fixedSize, boundingClientSize], compareRects);
263
+ const horizontalScrollTarget = cache('horizontalScrollTarget', getHorizontalScrollTarget, [options.horizontalScrollTarget, options.disableScrollOnHover, measuredColumns, pinning, internalMousePosition, fixedSize, totalSize, clientSize, borderWidth]);
264
+ const verticalScrollTarget = cache('verticalScrollTarget', getVerticalScrollTarget, [options.verticalScrollTarget, options.disableScrollOnHover, measuredRows, pinning, internalMousePosition, fixedSize, totalSize, clientSize, borderWidth]);
265
+ const horizontalScrollSpeed = cache('horizontalScrollSpeed', getResolvedScrollSpeed, [options.horizontalScrollSpeed]);
266
+ const verticalScrollSpeed = cache('verticalScrollSpeed', getResolvedScrollSpeed, [options.verticalScrollSpeed]);
267
+
268
+ // Dynamic allocation
269
+ const staticColumns = cache('staticColumns', getStaticColumns, [data, measuredColumns, pinning, scrollRect, fixedSize, borderWidth]);
270
+ const staticRows = cache('staticRows', getStaticRows, [data, measuredRows, pinning, scrollRect, fixedSize, borderWidth]);
160
271
 
161
272
  // Placement
162
- const columns = cache('columns', getPlacedColumns, [measuredColumns, devicePixelRatio, borderWidth]);
163
- const rows = cache('rows', getPlacedRows, [measuredRows, devicePixelRatio, borderWidth]);
273
+ const columns = cache('columns', getPlacedColumns, [staticColumns, pinning, borderWidth]);
274
+ const rows = cache('rows', getPlacedRows, [staticRows, pinning, borderWidth]);
164
275
  const columnLookup = cache('columnLookup', getLookup, [columns]);
165
276
  const rowLookup = cache('rowLookup', getLookup, [rows]);
166
277
  const focusedCell = options.focusedCell;
167
278
  const sections = cache('sections', getSections, [columns, rows]);
168
279
  const selectedCells = options.selectedCells;
169
- const fixedSize = cache('fixedSize', getFixedSize, [sections.top.height, sections.bottom.height, sections.left.width, sections.right.width]);
170
- const totalSize = cache('totalSize', getTotalSize, [columns, rows]);
171
- const hoveredCell = cache_value_deep('hoveredCell', getHoveredCell(element, context.mousePosition, rows, columns, fixedSize, totalSize));
280
+ const hoveredCell = cache('hoveredCell', getHoveredCell, [internalMousePosition, rows, columns, fixedSize, totalSize], compareCellIds);
172
281
  const isReordering = context.isReordering;
173
282
  // TODO: this does not seem to be cached properly (see onStateChange)
174
- const resizableColumn = context.resizingColumn || cache('resizableColumn', getResizableColumn, [columns, columnLookup, rowLookup, hoveredCell, element, context.mousePosition, isReordering, fixedSize, totalSize]);
175
- const resizableRow = context.resizingRow || cache('resizableRow', getResizableRow, [rows, columnLookup, rowLookup, hoveredCell, element, context.mousePosition, isReordering, fixedSize, totalSize]);
283
+ const resizableColumn = context.resizingColumn || cache('resizableColumn', getResizableColumn, [columns, columnLookup, rowLookup, hoveredCell, internalMousePosition, isReordering]);
284
+ const resizableRow = context.resizingRow || cache('resizableRow', getResizableRow, [rows, columnLookup, rowLookup, hoveredCell, internalMousePosition, isReordering]);
176
285
  const isMouseDown = context.isMouseDown;
177
286
  const canHighlight = !resizableColumn && !resizableRow && !context.didReorder;
178
287
  const highlightedCells = cache('highlightedCells', getHighlightedCells, [isMouseDown, canHighlight, focusedCell, hoveredCell, columns, rows, columnLookup, rowLookup]);
@@ -182,6 +291,9 @@ function updateStateInternal(context) {
182
291
  const renderFormatting = cache('renderFormatting', getRenderFormatting, [dataFormatting, hoveredCell, focusedCell, selection, highlight, edition, sortBy, resizableColumn, resizableRow, options.borderWidth, isReordering, context.reorderedColumn, context.reorderedRow]);
183
292
  const renderFormattingRules = cache('renderFormattingRules', getFormattingRules, [renderFormatting]);
184
293
  const renderFormatResolver = cache('renderFormatResolver', getFormatResolver, [renderFormattingRules, data, rows, columns, edition]);
294
+ const sortOrderFormatting = cache('sortOrderFormatting', getSortOrderFormatting, [dataFormatting]);
295
+ const sortOrderFormattingRules = cache('sortOrderFormattingRules', getFormattingRules, [sortOrderFormatting]);
296
+ const sortOrderFormatResolver = cache('sortOrderFormatResolver', getFormatResolver, [sortOrderFormattingRules, data, rows, columns, edition]);
185
297
  const inputFormatting = cache('inputFormatting', getInputFormatting, [dataFormatting]);
186
298
  const inputFormattingRules = cache('inputFormattingRules', getFormattingRules, [inputFormatting]);
187
299
  const inputFormatResolver = cache('inputFormatResolver', getFormatResolver, [inputFormattingRules, data, rows, columns, edition]);
@@ -192,8 +304,17 @@ function updateStateInternal(context) {
192
304
  const contextFormattingRules = cache('contextFormattingRules', getFormattingRules, [contextFormatting]);
193
305
  const contextFormatResolver = cache('contextFormatResolver', getFormatResolver, [contextFormattingRules, data, rows, columns, edition]);
194
306
  const tooltip = cache('tooltip', getTooltip, [hoveredCell, contextFormatResolver, columnLookup, rowLookup]);
195
- const tooltipPlacement = cache('tooltipPlacement', getTooltipPlacement, [tooltip, context.mousePosition]);
196
- const scrollRect = cache_value_deep('scrollRect', getScrollRect(previousState?.scrollRect, totalSize, fixedSize, element));
307
+ const tooltipPlacement = cache('tooltipPlacement', getTooltipPlacement, [tooltip, mousePosition]);
308
+
309
+ // Sub-states
310
+ const renderState = cache('renderState', getRenderState, [sections, devicePixelRatio, scrollRect, renderFormatResolver, borderWidth, textResolver]);
311
+ const inputState = cache('inputState', getInputState, [inputPlacement, isTextValid]);
312
+ const cursorState = cache('cursorState', getCursorState, [resizableColumn, resizableRow, context.isReordering]);
313
+ const tooltipState = cache('tooltipState', getTooltipState, [tooltip, tooltipPlacement]);
314
+ const scrollState = cache('scrollState', getScrollState, [verticalScrollTarget, horizontalScrollTarget, verticalScrollSpeed, horizontalScrollSpeed, memory.clientSize.version]);
315
+
316
+ // Style
317
+ const styleState = cache('styleState', getStyleState, [options.style], compareStyleStates);
197
318
 
198
319
  // callbacks
199
320
  const activeColumns = cache('activeColumns', getActiveColumns, [columns]);
@@ -207,42 +328,26 @@ function updateStateInternal(context) {
207
328
 
208
329
  context.state = {
209
330
  options,
210
- devicePixelRatio,
211
- borderWidth,
212
331
  edition,
213
332
  columns,
214
333
  rows,
215
- sections,
216
334
  hoveredCell,
217
335
  focusedCell,
218
- renderFormatResolver,
219
336
  inputFormatResolver,
220
337
  fixedSize,
221
338
  totalSize,
222
- textResolver,
223
- scrollRect,
339
+ clientSize,
340
+ scrollOffset,
224
341
  highlightedCells,
225
- inputPlacement,
226
342
  columnLookup,
227
343
  rowLookup,
228
344
  text,
229
- isTextValid,
230
- resizableColumn,
231
- resizableRow,
232
- tooltip,
233
- tooltipPlacement,
345
+ scrollState,
346
+ styleState,
347
+ sortOrderFormatResolver,
348
+ renderState,
349
+ inputState,
350
+ cursorState,
351
+ tooltipState,
234
352
  };
235
- }
236
-
237
- /**
238
- * @param {Context} context
239
- */
240
- export default function updateState(context) {
241
- if (!context.error) {
242
- try {
243
- updateStateInternal(context);
244
- } catch (error) {
245
- context.error = error;
246
- }
247
- }
248
353
  }
@@ -0,0 +1,26 @@
1
+ /** @import * as Types from "../typings.js"; */
2
+
3
+ /**
4
+ * @param {Types.Context} context
5
+ */
6
+ export default function style(context) {
7
+ // console.log('style', context.state.styleState);
8
+
9
+ const state = context.state.styleState;
10
+ const element = context.element;
11
+ const style = `
12
+ max-width: 100vw;
13
+ max-height: 100vh;
14
+ overflow: auto;
15
+ display: grid;
16
+ position: relative;
17
+ grid-template-columns: fit-content(0) fit-content(0) fit-content(0);
18
+ grid-template-rows: fit-content(0) fit-content(0) fit-content(0);
19
+ outline: none;
20
+ user-select: none;
21
+ touch-action: manipulation;
22
+ scrollbar-width: ${state.scrollbarWidth};
23
+ `
24
+
25
+ element.setAttribute('style', style);
26
+ }
@@ -0,0 +1,33 @@
1
+ /** @import * as Types from "../typings.js"; */
2
+
3
+ /**
4
+ * @param {Types.Context} context
5
+ */
6
+ export default function showTooltip(context) {
7
+ const element = context.element;
8
+ const tooltipElement = context.tooltip;
9
+ const tooltipState = context.state.tooltipState;
10
+ const content = tooltipState.tooltip;
11
+ const placement = tooltipState.tooltipPlacement;
12
+
13
+ if (!placement) {
14
+ if (tooltipElement.parentElement)
15
+ tooltipElement.parentElement.removeChild(tooltipElement);
16
+ return;
17
+ }
18
+
19
+ if (tooltipElement.innerHTML !== content) {
20
+ tooltipElement.innerHTML = content;
21
+ }
22
+
23
+ tooltipElement.style.left = `calc(anchor(left) + ${placement.left}px)`;
24
+ tooltipElement.style.top = `calc(anchor(top) + ${placement.top}px)`;
25
+
26
+ if (!tooltipElement.parentElement) {
27
+ element.appendChild(tooltipElement);
28
+ // @ts-ignore
29
+ tooltipElement.showPopover({
30
+ source: element,
31
+ });
32
+ }
33
+ }