@witchcraft/ui 0.3.15 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -2
- package/dist/runtime/components/LibTable/LibTable.vue +12 -4
- package/dist/runtime/directives/vResizableCols.js +101 -32
- package/package.json +2 -2
- package/src/module.ts +3 -0
- package/src/runtime/components/LibTable/LibTable.stories.ts +39 -45
- package/src/runtime/components/LibTable/LibTable.vue +12 -5
- package/src/runtime/directives/vResizableCols.ts +122 -35
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -22,7 +22,7 @@ const componentsInfo = globFiles([
|
|
|
22
22
|
name: name.startsWith("Lib") ? name.replace("Lib", "PREFIX") : `PREFIX${name}`,
|
|
23
23
|
filepath
|
|
24
24
|
}));
|
|
25
|
-
const module = defineNuxtModule({
|
|
25
|
+
const module$1 = defineNuxtModule({
|
|
26
26
|
meta: {
|
|
27
27
|
name: "witchcraftUi",
|
|
28
28
|
configKey: "witchcraftUi"
|
|
@@ -154,4 +154,4 @@ const module = defineNuxtModule({
|
|
|
154
154
|
}
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
export { module as default };
|
|
157
|
+
export { module$1 as default };
|
|
@@ -149,7 +149,10 @@
|
|
|
149
149
|
<tbody
|
|
150
150
|
:class="twMerge(
|
|
151
151
|
`table--body`,
|
|
152
|
-
isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic' && `
|
|
152
|
+
isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic' && `
|
|
153
|
+
grid
|
|
154
|
+
relative
|
|
155
|
+
`
|
|
153
156
|
)"
|
|
154
157
|
:style="{
|
|
155
158
|
...mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic' ? { height: `${totalSize}px` } : {}
|
|
@@ -162,9 +165,14 @@
|
|
|
162
165
|
<tr
|
|
163
166
|
:class="twMerge(
|
|
164
167
|
`
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic' && `
|
|
168
|
+
table--row
|
|
169
|
+
`,
|
|
170
|
+
isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic' && `
|
|
171
|
+
flex
|
|
172
|
+
absolute
|
|
173
|
+
w-full
|
|
174
|
+
`,
|
|
175
|
+
isPostSetup && mergedVirtualizerOpts.enabled && ` will-change-transform `
|
|
168
176
|
)"
|
|
169
177
|
:style="{
|
|
170
178
|
...mergedVirtualizerOpts.enabled ? {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { castType } from "@alanscodelog/utils/castType";
|
|
2
2
|
import { override } from "@alanscodelog/utils/override";
|
|
3
|
-
import { throttle } from "@alanscodelog/utils/throttle";
|
|
4
3
|
import { unreachable } from "@alanscodelog/utils/unreachable";
|
|
5
4
|
import { globalResizeObserver } from "../globalResizeObserver.js";
|
|
6
5
|
const observer = globalResizeObserver;
|
|
@@ -11,16 +10,21 @@ const defaultOpts = {
|
|
|
11
10
|
enabled: true
|
|
12
11
|
};
|
|
13
12
|
const callback = (_rect, el) => {
|
|
13
|
+
const $el = getElInfo(el);
|
|
14
|
+
if ($el.justResized) return;
|
|
14
15
|
setColWidths(el);
|
|
15
|
-
|
|
16
|
+
$el.justResized = true;
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
positionGrips(el);
|
|
19
|
+
$el.justResized = false;
|
|
20
|
+
}, 0);
|
|
16
21
|
};
|
|
17
|
-
const throttledCallback = throttle(callback);
|
|
18
22
|
export const vResizableCols = {
|
|
19
23
|
mounted(el, { value: opts = {} }) {
|
|
20
24
|
const options = override({ ...defaultOpts }, opts);
|
|
21
25
|
if (options.enabled) {
|
|
22
26
|
setupColumns(el, options);
|
|
23
|
-
observer.observe(el,
|
|
27
|
+
observer.observe(el, callback);
|
|
24
28
|
}
|
|
25
29
|
},
|
|
26
30
|
updated(el, { value: opts = {} }) {
|
|
@@ -28,21 +32,18 @@ export const vResizableCols = {
|
|
|
28
32
|
const info = el && options.enabled && getElInfo(el);
|
|
29
33
|
const hasGrips = el && options.enabled && elMap.get(el)?.grips;
|
|
30
34
|
const colsNotEqual = info && info.colCount !== options.colCount;
|
|
31
|
-
if (
|
|
35
|
+
if (!options.enabled || colsNotEqual) {
|
|
32
36
|
teardownColumns(el);
|
|
33
|
-
observer.unobserve(el,
|
|
37
|
+
observer.unobserve(el, callback);
|
|
34
38
|
}
|
|
35
39
|
if (!hasGrips && options.enabled || colsNotEqual) {
|
|
36
40
|
setupColumns(el, options);
|
|
37
|
-
observer.observe(el,
|
|
41
|
+
observer.observe(el, callback);
|
|
38
42
|
}
|
|
39
43
|
},
|
|
40
44
|
unmounted(el) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
teardownColumns(el);
|
|
44
|
-
globalResizeObserver.unobserve(el, throttledCallback);
|
|
45
|
-
}
|
|
45
|
+
teardownColumns(el);
|
|
46
|
+
globalResizeObserver.unobserve(el, callback);
|
|
46
47
|
},
|
|
47
48
|
getSSRProps() {
|
|
48
49
|
return {};
|
|
@@ -105,6 +106,7 @@ function createPointerMoveHandler(el) {
|
|
|
105
106
|
const $el = getElInfo(el);
|
|
106
107
|
if ($el.isDragging) {
|
|
107
108
|
e.preventDefault();
|
|
109
|
+
$el.fluidWidthsAsPercentOfFluidWidth = void 0;
|
|
108
110
|
const { col, colNext } = getCols(el);
|
|
109
111
|
if (col !== null) {
|
|
110
112
|
const leftBox = getBox(col);
|
|
@@ -131,7 +133,11 @@ function createPointerMoveHandler(el) {
|
|
|
131
133
|
setWidth(col, newWidth, el);
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
|
-
|
|
136
|
+
$el.justResized = true;
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
positionGrips(el);
|
|
139
|
+
$el.justResized = false;
|
|
140
|
+
}, 0);
|
|
135
141
|
}
|
|
136
142
|
}
|
|
137
143
|
};
|
|
@@ -173,9 +179,9 @@ function getTestGripSize(el) {
|
|
|
173
179
|
el.removeChild(testGrip);
|
|
174
180
|
return dynamicMinWidth;
|
|
175
181
|
}
|
|
176
|
-
function getElInfo(el) {
|
|
182
|
+
function getElInfo(el, { throwIfMissing = true } = {}) {
|
|
177
183
|
const $el = elMap.get(el);
|
|
178
|
-
if (!$el) unreachable("El went missing.");
|
|
184
|
+
if (!$el && throwIfMissing) unreachable("El went missing.");
|
|
179
185
|
return $el;
|
|
180
186
|
}
|
|
181
187
|
function getColEls(el) {
|
|
@@ -212,18 +218,22 @@ function setupColumns(el, opts) {
|
|
|
212
218
|
el.appendChild(grip);
|
|
213
219
|
$el.grips.set(grip, i);
|
|
214
220
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
$el.justResized = true;
|
|
222
|
+
setTimeout(() => {
|
|
223
|
+
positionGrips(el);
|
|
224
|
+
$el.justResized = false;
|
|
225
|
+
el.classList.add("resizable-cols-setup");
|
|
226
|
+
opts.onSetup?.(el);
|
|
227
|
+
}, 0);
|
|
218
228
|
}
|
|
219
229
|
function positionGrips(el) {
|
|
220
230
|
let xPos = 0;
|
|
221
231
|
const $el = getElInfo(el);
|
|
222
232
|
for (const grip of $el.grips.keys()) {
|
|
223
233
|
const col = $el.grips.get(grip);
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
226
|
-
const colBox = getBox(
|
|
234
|
+
const colEl = getColEls(el)[col];
|
|
235
|
+
if (!colEl) unreachable();
|
|
236
|
+
const colBox = getBox(colEl);
|
|
227
237
|
const gripBox = getBox(grip);
|
|
228
238
|
grip.style.left = `${xPos + colBox.width - gripBox.width / 2}px`;
|
|
229
239
|
xPos += colBox.width;
|
|
@@ -233,14 +243,71 @@ function setColWidths(el, children) {
|
|
|
233
243
|
const $el = getElInfo(el);
|
|
234
244
|
const header = children ?? getColEls(el).slice(0, $el.colCount);
|
|
235
245
|
const len = $el.colCount;
|
|
246
|
+
const elWidth = getBox(el).width;
|
|
247
|
+
let fluidTotalPx = 0;
|
|
248
|
+
const fluid = {};
|
|
249
|
+
const doCalculateFixed = $el.fixedWidths === void 0;
|
|
250
|
+
const doCalculateFluid = $el.fluidWidthsAsPercentOfFluidWidth === void 0;
|
|
251
|
+
if (doCalculateFixed) {
|
|
252
|
+
$el.fixedWidths = { [-1]: 0 };
|
|
253
|
+
}
|
|
254
|
+
if (doCalculateFluid) {
|
|
255
|
+
$el.fluidWidthsAsPercentOfFluidWidth = {};
|
|
256
|
+
}
|
|
257
|
+
for (let i = 0; i < len; i++) {
|
|
258
|
+
const col = header[i];
|
|
259
|
+
castType(col);
|
|
260
|
+
if (col.classList.contains("no-resize")) {
|
|
261
|
+
if (doCalculateFixed) {
|
|
262
|
+
const w = getBox(col).width;
|
|
263
|
+
$el.fixedWidths[i] = w;
|
|
264
|
+
$el.fixedWidths[-1] += $el.fixedWidths[i];
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
if (doCalculateFluid) {
|
|
268
|
+
const w = getBox(col).width;
|
|
269
|
+
fluid[i] = w;
|
|
270
|
+
fluidTotalPx += w;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const totalFluidCount = len - Object.keys($el.fixedWidths).length;
|
|
275
|
+
if (doCalculateFluid) {
|
|
276
|
+
for (let i = 0; i < len; i++) {
|
|
277
|
+
if ($el.fixedWidths[i] !== void 0) continue;
|
|
278
|
+
$el.fluidWidthsAsPercentOfFluidWidth[i] = fluid[i] / fluidTotalPx;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const fixedTotalPx = $el.fixedWidths[-1];
|
|
282
|
+
const minFlexWidth = totalFluidCount * $el.margin;
|
|
283
|
+
const minTotalWidth = minFlexWidth + fixedTotalPx;
|
|
284
|
+
let leftOverFluidWidth = elWidth - fixedTotalPx;
|
|
285
|
+
if (leftOverFluidWidth < minFlexWidth) {
|
|
286
|
+
leftOverFluidWidth = minFlexWidth;
|
|
287
|
+
}
|
|
236
288
|
let width = 0;
|
|
237
|
-
const minTotalWidth = len * $el.margin;
|
|
238
289
|
for (let i = 0; i < len; i++) {
|
|
239
290
|
const col = header[i];
|
|
240
291
|
castType(col);
|
|
241
292
|
const colBox = getBox(col);
|
|
242
|
-
|
|
243
|
-
|
|
293
|
+
if ($el.fixedWidths[i] !== void 0) {
|
|
294
|
+
setWidth(col, $el.fixedWidths[i], el);
|
|
295
|
+
width += $el.fixedWidths[i];
|
|
296
|
+
} else {
|
|
297
|
+
if ($el.fitWidth) {
|
|
298
|
+
if (!$el.widths.value[i]) {
|
|
299
|
+
setWidth(col, colBox.width, el);
|
|
300
|
+
width += getBox(col).width;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const newInPx = $el.fluidWidthsAsPercentOfFluidWidth[i] * leftOverFluidWidth;
|
|
304
|
+
setWidth(col, newInPx, el);
|
|
305
|
+
width += getBox(col).width;
|
|
306
|
+
} else {
|
|
307
|
+
setWidth(col, colBox.width, el);
|
|
308
|
+
width += getBox(col).width;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
244
311
|
}
|
|
245
312
|
if (width < minTotalWidth) {
|
|
246
313
|
el.style.minWidth = `${minTotalWidth}px`;
|
|
@@ -249,15 +316,17 @@ function setColWidths(el, children) {
|
|
|
249
316
|
}
|
|
250
317
|
}
|
|
251
318
|
function teardownColumns(el) {
|
|
252
|
-
const $el = getElInfo(el);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
319
|
+
const $el = getElInfo(el, { throwIfMissing: false });
|
|
320
|
+
if ($el) {
|
|
321
|
+
el.removeEventListener("pointerdown", $el.pointerDownHandler);
|
|
322
|
+
document.removeEventListener("pointermove", $el.pointerMoveHandler);
|
|
323
|
+
document.removeEventListener("pointerup", $el.pointerUpHandler);
|
|
324
|
+
for (const key of Object.keys($el)) {
|
|
325
|
+
delete $el[key];
|
|
326
|
+
}
|
|
327
|
+
$el.onTeardown?.(el);
|
|
328
|
+
elMap.delete(el);
|
|
258
329
|
}
|
|
259
|
-
elMap.delete(el);
|
|
260
330
|
el.classList.remove("resizable-cols-setup");
|
|
261
331
|
removeGrips(el);
|
|
262
|
-
$el.onTeardown?.(el);
|
|
263
332
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@witchcraft/ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16",
|
|
4
4
|
"description": "Vue component library.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/runtime/main.lib.js",
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
"storybook": "BROWSER=none storybook dev -p 6006",
|
|
179
179
|
"storybook:clear-cache": "BROWSER=none storybook dev -p 6006 --no-manager-cache",
|
|
180
180
|
"storybook:build": "pnpm nuxt prepare && storybook build -o docs/storybook",
|
|
181
|
-
"storybook:test": "pnpm storybook:build && pnpm concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm http-server docs/storybook --port 6006 --silent\" \"pnpm wait-on tcp:6006 && pnpm test-storybook\"",
|
|
181
|
+
"storybook:test": "pnpm storybook:build && pnpm concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm http-server docs/storybook --port 6006 --silent\" \"pnpm wait-on tcp:6006 && pnpm test-storybook --excludeTags 'skip-smoke-test'\"",
|
|
182
182
|
"test": "pnpm storybook:test && pnpm lint:types",
|
|
183
183
|
"test:dev": "pnpm concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm storybook\" \"pnpm wait-on tcp:6006 && pnpm test-storybook --watch\"",
|
|
184
184
|
"doc": "pnpm test && pnpm storybook:build",
|
package/src/module.ts
CHANGED
|
@@ -177,7 +177,9 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
177
177
|
nuxt.hook("vite:extendConfig", async config => {
|
|
178
178
|
if (options.includeUnpluginIconsPlugins) {
|
|
179
179
|
logger.info(`Adding unplugin-icons`)
|
|
180
|
+
// @ts-expect-error .
|
|
180
181
|
config.plugins ??= []
|
|
182
|
+
// @ts-expect-error .
|
|
181
183
|
config.plugins = [
|
|
182
184
|
...(
|
|
183
185
|
options.includeUnpluginIconsPlugins
|
|
@@ -199,6 +201,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
199
201
|
tailwindcss() as any,
|
|
200
202
|
...config.plugins
|
|
201
203
|
]
|
|
204
|
+
// @ts-expect-error .
|
|
202
205
|
config.optimizeDeps ??= {}
|
|
203
206
|
config.optimizeDeps.exclude ??= []
|
|
204
207
|
config.optimizeDeps.exclude.push("~icons")
|
|
@@ -26,8 +26,8 @@ export const Primary: Story = {
|
|
|
26
26
|
components,
|
|
27
27
|
setup: () => {
|
|
28
28
|
const show = ref(true)
|
|
29
|
+
const debugGrips = ref(false)
|
|
29
30
|
// careful, storybook passes refs as is causing issues
|
|
30
|
-
//
|
|
31
31
|
const argsReactive = reactive({
|
|
32
32
|
...args,
|
|
33
33
|
resizable: {
|
|
@@ -38,24 +38,26 @@ export const Primary: Story = {
|
|
|
38
38
|
|
|
39
39
|
return {
|
|
40
40
|
args: argsReactive,
|
|
41
|
+
debugGrips,
|
|
41
42
|
show
|
|
42
43
|
}
|
|
43
44
|
},
|
|
44
45
|
template: `
|
|
45
46
|
<div class="p-2 flex flex-col gap-2 border rounded-md mb-10">
|
|
46
47
|
Controls:
|
|
47
|
-
<
|
|
48
|
-
<LibButton class="flex-1" @click="args.resizable.enabled = !args.resizable.enabled">Toggle Resizable (currently {{args.resizable.enabled}})</LibButton>
|
|
49
|
-
</div>
|
|
48
|
+
<LibButton class="flex-1" @click="args.resizable.enabled = !args.resizable.enabled">Toggle Resizable (currently {{args.resizable.enabled}})</LibButton>
|
|
50
49
|
<LibButton @click="args.stickyHeader = !args.stickyHeader">Toggle Sticky Header (currently {{args.stickyHeader}})</LibButton>
|
|
51
50
|
<LibButton @click="show = !show">Toggle Table</LibButton>
|
|
51
|
+
<LibButton @click="debugGrips = !debugGrips">Toggle Debug Grips (currently {{debugGrips}})</LibButton>
|
|
52
|
+
</div>
|
|
53
|
+
<div :class="debugGrips ? ' [&_.grip]:bg-red-500': ''">
|
|
54
|
+
<lib-table
|
|
55
|
+
v-if="show"
|
|
56
|
+
v-bind="args"
|
|
57
|
+
>
|
|
58
|
+
${(args as any).slots}
|
|
59
|
+
</lib-table>
|
|
52
60
|
</div>
|
|
53
|
-
<lib-table
|
|
54
|
-
v-if="show"
|
|
55
|
-
v-bind="args"
|
|
56
|
-
>
|
|
57
|
-
${(args as any).slots}
|
|
58
|
-
</lib-table>
|
|
59
61
|
`
|
|
60
62
|
}),
|
|
61
63
|
args: {
|
|
@@ -235,6 +237,9 @@ export const VirtualizedDynamicHeightExperimental: Story = {
|
|
|
235
237
|
prop2: `Item${i + 1} Prop 2: ${faker.helpers.arrayElement(fakerSentences)}`,
|
|
236
238
|
prop3: `Item${i + 1} Prop 3: ${faker.helpers.arrayElement(fakerSentences)}`
|
|
237
239
|
}))
|
|
240
|
+
},
|
|
241
|
+
parameters: {
|
|
242
|
+
tags: ["skip-smoke-test"]
|
|
238
243
|
}
|
|
239
244
|
}
|
|
240
245
|
|
|
@@ -268,22 +273,30 @@ export const VirtualizedFitWidthFalse: Story = {
|
|
|
268
273
|
export const ThreeColSomeColsNotResizable: Story = {
|
|
269
274
|
render: args => ({
|
|
270
275
|
components,
|
|
271
|
-
setup: () =>
|
|
276
|
+
setup: () => {
|
|
277
|
+
const debugGrips = ref(false)
|
|
278
|
+
return { args, debugGrips }
|
|
279
|
+
},
|
|
272
280
|
template: `
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
281
|
+
<div class="flex flex-col gap-2 w-full border rounded-md mb-10">
|
|
282
|
+
<WButton @click="debugGrips = !debugGrips">Toggle Debug Grips (currently {{debugGrips}})</WButton>
|
|
283
|
+
</div>
|
|
284
|
+
<div :class="debugGrips ? ' [&_.grip]:bg-red-500': ''">
|
|
285
|
+
<div class="flex flex-col gap-2 w-full">
|
|
286
|
+
<lib-table
|
|
287
|
+
v-bind="args"
|
|
288
|
+
>
|
|
289
|
+
</lib-table>
|
|
290
|
+
<lib-table
|
|
291
|
+
v-bind="{...args, colConfig:args.colConfig2}"
|
|
292
|
+
>
|
|
293
|
+
</lib-table>
|
|
294
|
+
<lib-table
|
|
295
|
+
v-bind="{...args, colConfig:args.colConfig3}"
|
|
296
|
+
>
|
|
297
|
+
</lib-table>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
287
300
|
`
|
|
288
301
|
}),
|
|
289
302
|
args: {
|
|
@@ -298,26 +311,7 @@ export const ThreeColSomeColsNotResizable: Story = {
|
|
|
298
311
|
} as any
|
|
299
312
|
}
|
|
300
313
|
export const FourColSomeColsNotResizable: Story = {
|
|
301
|
-
render:
|
|
302
|
-
components,
|
|
303
|
-
setup: () => ({ args }),
|
|
304
|
-
template: `
|
|
305
|
-
<lib-table
|
|
306
|
-
v-bind="args"
|
|
307
|
-
>
|
|
308
|
-
</lib-table>
|
|
309
|
-
<br>
|
|
310
|
-
<lib-table
|
|
311
|
-
v-bind="{...args, colConfig:args.colConfig2}"
|
|
312
|
-
>
|
|
313
|
-
</lib-table>
|
|
314
|
-
<br>
|
|
315
|
-
<lib-table
|
|
316
|
-
v-bind="{...args, colConfig:args.colConfig3}"
|
|
317
|
-
>
|
|
318
|
-
</lib-table>
|
|
319
|
-
`
|
|
320
|
-
}),
|
|
314
|
+
render: ThreeColSomeColsNotResizable.render,
|
|
321
315
|
args: {
|
|
322
316
|
cols: ["prop1", "prop2", "prop3", "prop4"],
|
|
323
317
|
values: [
|
|
@@ -150,8 +150,10 @@
|
|
|
150
150
|
<tbody
|
|
151
151
|
:class="twMerge(
|
|
152
152
|
`table--body`,
|
|
153
|
-
isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic'
|
|
154
|
-
|
|
153
|
+
isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic' && `
|
|
154
|
+
grid
|
|
155
|
+
relative
|
|
156
|
+
`
|
|
155
157
|
)"
|
|
156
158
|
:style="{
|
|
157
159
|
...(mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic'
|
|
@@ -165,9 +167,14 @@
|
|
|
165
167
|
>
|
|
166
168
|
<tr
|
|
167
169
|
:class="twMerge(`
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
&&
|
|
170
|
+
table--row
|
|
171
|
+
`,
|
|
172
|
+
isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic' && `
|
|
173
|
+
flex
|
|
174
|
+
absolute
|
|
175
|
+
w-full
|
|
176
|
+
`,
|
|
177
|
+
isPostSetup && mergedVirtualizerOpts.enabled && ` will-change-transform `
|
|
171
178
|
)"
|
|
172
179
|
:style="{
|
|
173
180
|
...(mergedVirtualizerOpts.enabled
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { castType } from "@alanscodelog/utils/castType"
|
|
2
2
|
import { override } from "@alanscodelog/utils/override"
|
|
3
|
-
import { throttle } from "@alanscodelog/utils/throttle"
|
|
4
3
|
import { unreachable } from "@alanscodelog/utils/unreachable"
|
|
5
4
|
import type { Directive, Ref } from "vue"
|
|
6
5
|
|
|
@@ -23,6 +22,9 @@ type Data = {
|
|
|
23
22
|
widths: Ref<string[]>
|
|
24
23
|
selector: string
|
|
25
24
|
onTeardown?: (el: Element) => void
|
|
25
|
+
fixedWidths?: Record<number, number>
|
|
26
|
+
fluidWidthsAsPercentOfFluidWidth?: Record<number, number>
|
|
27
|
+
justResized?: boolean
|
|
26
28
|
}
|
|
27
29
|
const elMap = new WeakMap<HTMLElement, Data>()
|
|
28
30
|
type RawOpts = { value: Partial<ResizableOptions> }
|
|
@@ -35,11 +37,19 @@ const defaultOpts: Omit<ResizableOptions, "colCount" | "widths" | "selector"> =
|
|
|
35
37
|
enabled: true
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
// note that while it would be nice to throttle this it seems to loose the reference to the original element
|
|
41
|
+
// haven't found where the issue is yet #future
|
|
38
42
|
const callback: ResizeCallback = (_rect: DOMRectReadOnly, el: Element): void => {
|
|
43
|
+
const $el = getElInfo(el as ResizableElement)
|
|
44
|
+
if ($el.justResized) return
|
|
39
45
|
setColWidths(el as ResizableElement)
|
|
40
|
-
|
|
46
|
+
$el.justResized = true
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
positionGrips(el as ResizableElement)
|
|
49
|
+
$el.justResized = false
|
|
50
|
+
}, 0)
|
|
41
51
|
}
|
|
42
|
-
|
|
52
|
+
|
|
43
53
|
/**
|
|
44
54
|
* Allow a table like element to be resized along it's columns.
|
|
45
55
|
*
|
|
@@ -104,7 +114,7 @@ export const vResizableCols: Directive = {
|
|
|
104
114
|
|
|
105
115
|
if (options.enabled) {
|
|
106
116
|
setupColumns(el, options)
|
|
107
|
-
observer.observe(el,
|
|
117
|
+
observer.observe(el, callback)
|
|
108
118
|
}
|
|
109
119
|
},
|
|
110
120
|
updated(el: ResizableElement, { value: opts = {} }: RawOpts) {
|
|
@@ -113,22 +123,19 @@ export const vResizableCols: Directive = {
|
|
|
113
123
|
const hasGrips = el && options.enabled && elMap.get(el)?.grips
|
|
114
124
|
// todo, we should probably check by name
|
|
115
125
|
const colsNotEqual = (info && info.colCount !== options.colCount)
|
|
116
|
-
if (
|
|
126
|
+
if (!options.enabled || colsNotEqual) {
|
|
117
127
|
teardownColumns(el)
|
|
118
|
-
observer.unobserve(el,
|
|
128
|
+
observer.unobserve(el, callback)
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
if ((!hasGrips && options.enabled) || colsNotEqual) {
|
|
122
132
|
setupColumns(el, options)
|
|
123
|
-
observer.observe(el,
|
|
133
|
+
observer.observe(el, callback)
|
|
124
134
|
}
|
|
125
135
|
},
|
|
126
136
|
unmounted(el: ResizableElement) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
teardownColumns(el)
|
|
130
|
-
globalResizeObserver.unobserve(el, throttledCallback)
|
|
131
|
-
}
|
|
137
|
+
teardownColumns(el)
|
|
138
|
+
globalResizeObserver.unobserve(el, callback)
|
|
132
139
|
},
|
|
133
140
|
getSSRProps() {
|
|
134
141
|
return {}
|
|
@@ -203,6 +210,8 @@ function createPointerMoveHandler(el: ResizableElement) {
|
|
|
203
210
|
if ($el.isDragging) {
|
|
204
211
|
e.preventDefault()
|
|
205
212
|
|
|
213
|
+
$el.fluidWidthsAsPercentOfFluidWidth = undefined
|
|
214
|
+
|
|
206
215
|
const { col, colNext } = getCols(el)
|
|
207
216
|
|
|
208
217
|
if (col !== null) {
|
|
@@ -236,7 +245,11 @@ function createPointerMoveHandler(el: ResizableElement) {
|
|
|
236
245
|
}
|
|
237
246
|
}
|
|
238
247
|
|
|
239
|
-
|
|
248
|
+
$el.justResized = true
|
|
249
|
+
setTimeout(() => {
|
|
250
|
+
positionGrips(el)
|
|
251
|
+
$el.justResized = false
|
|
252
|
+
}, 0)
|
|
240
253
|
}
|
|
241
254
|
}
|
|
242
255
|
}
|
|
@@ -282,10 +295,10 @@ function getTestGripSize(el: ResizableElement): number {
|
|
|
282
295
|
return dynamicMinWidth
|
|
283
296
|
}
|
|
284
297
|
|
|
285
|
-
function getElInfo(el: ResizableElement): Data {
|
|
298
|
+
function getElInfo<T extends boolean = true>(el: ResizableElement, { throwIfMissing = true as T }: { throwIfMissing?: T } = {}): T extends true ? Data : Data | undefined {
|
|
286
299
|
const $el = elMap.get(el)
|
|
287
|
-
if (!$el) unreachable("El went missing.")
|
|
288
|
-
return $el
|
|
300
|
+
if (!$el && throwIfMissing) unreachable("El went missing.")
|
|
301
|
+
return $el as any
|
|
289
302
|
}
|
|
290
303
|
function getColEls(el: ResizableElement): HTMLElement[] {
|
|
291
304
|
const $el = elMap.get(el)
|
|
@@ -325,9 +338,13 @@ function setupColumns(el: ResizableElement, opts: ResizableOptions): void {
|
|
|
325
338
|
el.appendChild(grip)
|
|
326
339
|
$el.grips.set(grip, i)
|
|
327
340
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
341
|
+
$el.justResized = true
|
|
342
|
+
setTimeout(() => {
|
|
343
|
+
positionGrips(el)
|
|
344
|
+
$el.justResized = false
|
|
345
|
+
el.classList.add("resizable-cols-setup")
|
|
346
|
+
opts.onSetup?.(el)
|
|
347
|
+
}, 0)
|
|
331
348
|
}
|
|
332
349
|
|
|
333
350
|
function positionGrips(el: ResizableElement): void {
|
|
@@ -335,9 +352,9 @@ function positionGrips(el: ResizableElement): void {
|
|
|
335
352
|
const $el = getElInfo(el)
|
|
336
353
|
for (const grip of $el.grips.keys()) {
|
|
337
354
|
const col = $el.grips.get(grip)!
|
|
338
|
-
const
|
|
339
|
-
if (!
|
|
340
|
-
const colBox = getBox(
|
|
355
|
+
const colEl = getColEls(el)[col]
|
|
356
|
+
if (!colEl) unreachable()
|
|
357
|
+
const colBox = getBox(colEl)
|
|
341
358
|
const gripBox = getBox(grip)
|
|
342
359
|
|
|
343
360
|
grip.style.left = `${xPos + colBox.width - (gripBox.width / 2)}px`
|
|
@@ -349,8 +366,60 @@ function setColWidths(el: ResizableElement, children?: Element[]): void {
|
|
|
349
366
|
const $el = getElInfo(el)
|
|
350
367
|
const header = children ?? getColEls(el).slice(0, $el.colCount)
|
|
351
368
|
const len = $el.colCount
|
|
369
|
+
const elWidth = getBox(el).width
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
let fluidTotalPx = 0
|
|
373
|
+
const fluid: Record<number, number> = {}
|
|
374
|
+
|
|
375
|
+
const doCalculateFixed = $el.fixedWidths === undefined
|
|
376
|
+
const doCalculateFluid = $el.fluidWidthsAsPercentOfFluidWidth === undefined
|
|
377
|
+
|
|
378
|
+
if (doCalculateFixed) {
|
|
379
|
+
$el.fixedWidths = { [-1]: 0 } // fixedTotalWidth
|
|
380
|
+
}
|
|
381
|
+
if (doCalculateFluid) {
|
|
382
|
+
$el.fluidWidthsAsPercentOfFluidWidth = {}
|
|
383
|
+
}
|
|
384
|
+
for (let i = 0; i < len; i++) {
|
|
385
|
+
const col = header[i]
|
|
386
|
+
castType<HTMLElement>(col)
|
|
387
|
+
if (col.classList.contains("no-resize")) {
|
|
388
|
+
if (doCalculateFixed) {
|
|
389
|
+
const w = getBox(col).width
|
|
390
|
+
$el.fixedWidths![i] = w
|
|
391
|
+
$el.fixedWidths![-1]! += $el.fixedWidths![i]!
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
if (doCalculateFluid) {
|
|
395
|
+
const w = getBox(col).width
|
|
396
|
+
fluid[i] = w
|
|
397
|
+
fluidTotalPx += w
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const totalFluidCount = len - Object.keys($el.fixedWidths!).length
|
|
403
|
+
|
|
404
|
+
if (doCalculateFluid) {
|
|
405
|
+
for (let i = 0; i < len; i++) {
|
|
406
|
+
if ($el.fixedWidths![i] !== undefined) continue
|
|
407
|
+
$el.fluidWidthsAsPercentOfFluidWidth![i] = fluid[i]! / fluidTotalPx
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const fixedTotalPx = $el.fixedWidths![-1]!
|
|
412
|
+
const minFlexWidth = (totalFluidCount * $el.margin)
|
|
413
|
+
const minTotalWidth = minFlexWidth + fixedTotalPx
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
let leftOverFluidWidth = elWidth - fixedTotalPx
|
|
417
|
+
if (leftOverFluidWidth < minFlexWidth) {
|
|
418
|
+
leftOverFluidWidth = minFlexWidth
|
|
419
|
+
}
|
|
420
|
+
|
|
352
421
|
let width = 0
|
|
353
|
-
|
|
422
|
+
|
|
354
423
|
for (let i = 0; i < len; i++) {
|
|
355
424
|
const col = header[i]
|
|
356
425
|
castType<HTMLElement>(col)
|
|
@@ -358,9 +427,24 @@ function setColWidths(el: ResizableElement, children?: Element[]): void {
|
|
|
358
427
|
* only works if parent table does NOT use `box-sizing:border-box` and either has no border or does `width: calc(100% - BORDERWIDTH*2)`
|
|
359
428
|
*/
|
|
360
429
|
const colBox = getBox(col)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
430
|
+
if ($el.fixedWidths![i] !== undefined) {
|
|
431
|
+
setWidth(col, $el.fixedWidths![i]!, el)
|
|
432
|
+
width += $el.fixedWidths![i]!
|
|
433
|
+
} else {
|
|
434
|
+
if ($el.fitWidth) {
|
|
435
|
+
if (!$el.widths.value[i]) {
|
|
436
|
+
setWidth(col, colBox.width, el)
|
|
437
|
+
width += getBox(col).width
|
|
438
|
+
continue
|
|
439
|
+
}
|
|
440
|
+
const newInPx = $el.fluidWidthsAsPercentOfFluidWidth![i]! * leftOverFluidWidth
|
|
441
|
+
setWidth(col, newInPx, el)
|
|
442
|
+
width += getBox(col).width
|
|
443
|
+
} else {
|
|
444
|
+
setWidth(col, colBox.width, el)
|
|
445
|
+
width += getBox(col).width
|
|
446
|
+
}
|
|
447
|
+
}
|
|
364
448
|
}
|
|
365
449
|
|
|
366
450
|
if (width < minTotalWidth) {
|
|
@@ -371,16 +455,19 @@ function setColWidths(el: ResizableElement, children?: Element[]): void {
|
|
|
371
455
|
}
|
|
372
456
|
|
|
373
457
|
function teardownColumns(el: ResizableElement): void {
|
|
374
|
-
const $el = getElInfo(el)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
458
|
+
const $el = getElInfo(el, { throwIfMissing: false })
|
|
459
|
+
|
|
460
|
+
if ($el) {
|
|
461
|
+
el.removeEventListener("pointerdown", $el.pointerDownHandler)
|
|
462
|
+
document.removeEventListener("pointermove", $el.pointerMoveHandler)
|
|
463
|
+
document.removeEventListener("pointerup", $el.pointerUpHandler)
|
|
464
|
+
for (const key of Object.keys($el)) {
|
|
465
|
+
delete $el[key as keyof typeof $el]
|
|
466
|
+
}
|
|
467
|
+
$el.onTeardown?.(el)
|
|
468
|
+
elMap.delete(el)
|
|
381
469
|
}
|
|
382
|
-
|
|
470
|
+
|
|
383
471
|
el.classList.remove("resizable-cols-setup")
|
|
384
472
|
removeGrips(el)
|
|
385
|
-
$el.onTeardown?.(el)
|
|
386
473
|
}
|