@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "witchcraftUi",
3
3
  "configKey": "witchcraftUi",
4
- "version": "0.3.15",
4
+ "version": "0.3.16",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
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' && `grid relative`
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
- table--row
166
- `,
167
- isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic' && `flex absolute w-full`
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
- positionGrips(el);
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, throttledCallback);
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 (hasGrips && !options.enabled || colsNotEqual) {
35
+ if (!options.enabled || colsNotEqual) {
32
36
  teardownColumns(el);
33
- observer.unobserve(el, throttledCallback);
37
+ observer.unobserve(el, callback);
34
38
  }
35
39
  if (!hasGrips && options.enabled || colsNotEqual) {
36
40
  setupColumns(el, options);
37
- observer.observe(el, throttledCallback);
41
+ observer.observe(el, callback);
38
42
  }
39
43
  },
40
44
  unmounted(el) {
41
- const hasGrips = elMap.has(el) && elMap.get(el).grips;
42
- if (hasGrips) {
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
- positionGrips(el);
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
- positionGrips(el);
216
- el.classList.add("resizable-cols-setup");
217
- opts.onSetup?.(el);
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 colEls = getColEls(el)[col];
225
- if (!colEls) unreachable();
226
- const colBox = getBox(colEls);
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
- setWidth(col, colBox.width, el);
243
- width += getBox(col).width;
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
- el.removeEventListener("pointerdown", $el.pointerDownHandler);
254
- document.removeEventListener("pointermove", $el.pointerMoveHandler);
255
- document.removeEventListener("pointerup", $el.pointerUpHandler);
256
- for (const key of Object.keys($el)) {
257
- delete $el[key];
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.15",
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
- <div class="flex gap-2 w-full">
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: () => ({ args }),
276
+ setup: () => {
277
+ const debugGrips = ref(false)
278
+ return { args, debugGrips }
279
+ },
272
280
  template: `
273
- <lib-table
274
- v-bind="args"
275
- >
276
- </lib-table>
277
- <br>
278
- <lib-table
279
- v-bind="{...args, colConfig:args.colConfig2}"
280
- >
281
- </lib-table>
282
- <br>
283
- <lib-table
284
- v-bind="{...args, colConfig:args.colConfig3}"
285
- >
286
- </lib-table>
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: args => ({
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
- && `grid relative`
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
- table--row
169
- `, isPostSetup && mergedVirtualizerOpts.enabled && mergedVirtualizerOpts.method === 'dynamic'
170
- && `flex absolute w-full`
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
- positionGrips(el as ResizableElement)
46
+ $el.justResized = true
47
+ setTimeout(() => {
48
+ positionGrips(el as ResizableElement)
49
+ $el.justResized = false
50
+ }, 0)
41
51
  }
42
- const throttledCallback = throttle(callback)
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, throttledCallback)
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 ((hasGrips && !options.enabled) || colsNotEqual) {
126
+ if (!options.enabled || colsNotEqual) {
117
127
  teardownColumns(el)
118
- observer.unobserve(el, throttledCallback)
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, throttledCallback)
133
+ observer.observe(el, callback)
124
134
  }
125
135
  },
126
136
  unmounted(el: ResizableElement) {
127
- const hasGrips = elMap.has(el) && elMap.get(el)!.grips
128
- if (hasGrips) {
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
- positionGrips(el)
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
- positionGrips(el)
329
- el.classList.add("resizable-cols-setup")
330
- opts.onSetup?.(el)
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 colEls = getColEls(el)[col]
339
- if (!colEls) unreachable()
340
- const colBox = getBox(colEls)
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
- const minTotalWidth = len * $el.margin
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
- setWidth(col, colBox.width, el)
363
- width += getBox(col).width
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
- el.removeEventListener("pointerdown", $el.pointerDownHandler)
377
- document.removeEventListener("pointermove", $el.pointerMoveHandler)
378
- document.removeEventListener("pointerup", $el.pointerUpHandler)
379
- for (const key of Object.keys($el)) {
380
- delete $el[key as keyof typeof $el]
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
- elMap.delete(el)
470
+
383
471
  el.classList.remove("resizable-cols-setup")
384
472
  removeGrips(el)
385
- $el.onTeardown?.(el)
386
473
  }