svelte-flexiboards 0.4.0 → 0.4.1

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.
@@ -10,21 +10,9 @@
10
10
  import RenderedFlexiWidget from './rendered-flexi-widget.svelte';
11
11
  import { assistiveTextStyle, generateUniqueId } from '../system/shared/utils.svelte.js';
12
12
 
13
- /** @deprecated FlexiAdd's children props are now redundant and will be removed in v0.4. */
14
- type FlexiAddChildrenProps = {
15
- /**
16
- * @deprecated This has been replaced with internal pointer management and is redundant. This event will be removed in v0.4.
17
- */
18
- onpointerdown: (event: PointerEvent) => void;
19
- /**
20
- * @deprecated This has been moved to the internal button so is now redundant. It will be removed in v0.4.
21
- */
22
- style: string;
23
- };
24
-
25
13
  export type FlexiAddProps = FlexiCommonProps<FlexiAddController> & {
26
14
  class?: FlexiAddClasses;
27
- children?: Snippet<[{ adder: FlexiAddController; props: FlexiAddChildrenProps }]>;
15
+ children?: Snippet<[{ adder: FlexiAddController }]>;
28
16
  addWidget: FlexiAddWidgetFn;
29
17
  };
30
18
  </script>
@@ -75,7 +63,7 @@
75
63
  <span style={assistiveTextStyle} id={assistiveTextId}>
76
64
  Press Enter to drag a new widget into this board.
77
65
  </span>
78
- {@render children?.({ adder, props: { style: '', onpointerdown: dummyOnpointerdown } })}
66
+ {@render children?.({ adder })}
79
67
  </button>
80
68
 
81
69
  <div style="display: none;">
@@ -1,22 +1,10 @@
1
1
  import { type FlexiAddController, type FlexiAddClasses, type FlexiAddWidgetFn } from '../system/misc/adder.svelte.js';
2
2
  import { type Snippet } from 'svelte';
3
3
  import type { FlexiCommonProps } from '../system/types.js';
4
- /** @deprecated FlexiAdd's children props are now redundant and will be removed in v0.4. */
5
- type FlexiAddChildrenProps = {
6
- /**
7
- * @deprecated This has been replaced with internal pointer management and is redundant. This event will be removed in v0.4.
8
- */
9
- onpointerdown: (event: PointerEvent) => void;
10
- /**
11
- * @deprecated This has been moved to the internal button so is now redundant. It will be removed in v0.4.
12
- */
13
- style: string;
14
- };
15
4
  export type FlexiAddProps = FlexiCommonProps<FlexiAddController> & {
16
5
  class?: FlexiAddClasses;
17
6
  children?: Snippet<[{
18
7
  adder: FlexiAddController;
19
- props: FlexiAddChildrenProps;
20
8
  }]>;
21
9
  addWidget: FlexiAddWidgetFn;
22
10
  };
@@ -8,18 +8,6 @@
8
8
  } from '../system/misc/deleter.svelte.js';
9
9
  import { assistiveTextStyle, generateUniqueId } from '../system/shared/utils.svelte.js';
10
10
 
11
- /** @deprecated FlexiDelete's children props are now redundant and will be removed in v0.4. */
12
- type FlexiDeleteChildrenProps = {
13
- /**
14
- * @deprecated This has been replaced with internal pointer management and is redundant. These events will be removed in v0.4.
15
- */
16
- onpointerenter: (event: PointerEvent) => void;
17
- /**
18
- * @deprecated This has been replaced with internal pointer management and is redundant. These events will be removed in v0.4.
19
- */
20
- onpointerleave: (event: PointerEvent) => void;
21
- };
22
-
23
11
  export type FlexiDeleteProps = FlexiCommonProps<FlexiDeleteController> & {
24
12
  class?: FlexiDeleteClasses;
25
13
  children?: Snippet<[{ deleter: FlexiDeleteController }]>;
@@ -58,6 +58,10 @@ export class InternalFlexiBoardController {
58
58
  if (event.board !== this) {
59
59
  return;
60
60
  }
61
+ // No point exporting if nobody is listening
62
+ if (!this.config?.onLayoutChange && !this.#responsiveController) {
63
+ return;
64
+ }
61
65
  // Debounce the callback
62
66
  if (this.#layoutChangeTimeout) {
63
67
  clearTimeout(this.#layoutChangeTimeout);
@@ -60,6 +60,8 @@ export declare class GridDimensionTracker {
60
60
  refreshScrollListeners(): void;
61
61
  getCellFromPointerPosition(clientX: number, clientY: number): CellPosition | null;
62
62
  }
63
+ export declare function contentSize(axisCoordinates: number[], gap: number): number;
64
+ export declare function findCell(pointerLocation: number, start: number, size: number, gap: number, axisCoordinates: number[]): number;
63
65
  export declare function generateUniqueId(prefix?: string): string;
64
66
  export declare const assistiveTextStyle = "\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip: rect(0, 0, 0, 0);\n\twhite-space: nowrap;\n\tborder-width: 0;\n";
65
67
  export declare function getElementMidpoint(element: HTMLElement): {
@@ -48,7 +48,11 @@ export class PointerService {
48
48
  isPointerInside(element) {
49
49
  const rect = element.getBoundingClientRect();
50
50
  const { x, y } = this.#position;
51
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
51
+ // Use scrollWidth/scrollHeight to account for content that overflows the element
52
+ // (e.g. grid columns that extend beyond a scrollable parent with overflow: hidden).
53
+ const width = Math.max(rect.width, element.scrollWidth);
54
+ const height = Math.max(rect.height, element.scrollHeight);
55
+ return x >= rect.left && x <= rect.left + width && y >= rect.top && y <= rect.top + height;
52
56
  }
53
57
  get keyboardControlsActive() {
54
58
  return this.#keyboardController.active;
@@ -557,9 +561,9 @@ export class GridDimensionTracker {
557
561
  const style = window.getComputedStyle(parent);
558
562
  const overflowY = style.getPropertyValue('overflow-y');
559
563
  const overflowX = style.getPropertyValue('overflow-x');
560
- const isScrollable = ((overflowY === 'auto' || overflowY === 'scroll') &&
564
+ const isScrollable = ((overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'hidden') &&
561
565
  parent.scrollHeight > parent.clientHeight) ||
562
- ((overflowX === 'auto' || overflowX === 'scroll') &&
566
+ ((overflowX === 'auto' || overflowX === 'scroll' || overflowX === 'hidden') &&
563
567
  parent.scrollWidth > parent.clientWidth);
564
568
  if (isScrollable) {
565
569
  ancestors.push(parent);
@@ -574,32 +578,45 @@ export class GridDimensionTracker {
574
578
  }
575
579
  this.#pointerPosition.x = clientX;
576
580
  this.#pointerPosition.y = clientY;
577
- let xCell = this.#findCell(clientX, this.#dimensions.left, this.#dimensions.width, this.#dimensions.columnGap, this.#dimensions.columns);
578
- let yCell = this.#findCell(clientY, this.#dimensions.top, this.#dimensions.height, this.#dimensions.rowGap, this.#dimensions.rows);
581
+ // DEBUG: trace scroll offset issue
582
+ const gridRef = this.#grid.ref;
583
+ const freshRect = gridRef.getBoundingClientRect();
584
+ // Walk up to find the scrollable parent (board element)
585
+ let scrollParent = gridRef.parentElement;
586
+ while (scrollParent && scrollParent.scrollLeft === 0 && scrollParent !== document.documentElement) {
587
+ scrollParent = scrollParent.parentElement;
588
+ }
589
+ let xCell = findCell(clientX, this.#dimensions.left, contentSize(this.#dimensions.columns, this.#dimensions.columnGap), this.#dimensions.columnGap, this.#dimensions.columns);
590
+ let yCell = findCell(clientY, this.#dimensions.top, contentSize(this.#dimensions.rows, this.#dimensions.rowGap), this.#dimensions.rowGap, this.#dimensions.rows);
579
591
  return {
580
592
  row: yCell,
581
593
  column: xCell
582
594
  };
583
595
  }
584
- #findCell(pointerLocation, start, size, gap, axisCoordinates) {
585
- // If outside the axis, then return the ends.
586
- if (pointerLocation < start) {
587
- return 0;
588
- }
589
- if (pointerLocation >= start + size) {
590
- return axisCoordinates.length;
591
- }
592
- let subtotal = start - gap / 2;
593
- for (let i = 0; i < axisCoordinates.length; i++) {
594
- const base = subtotal;
595
- subtotal += axisCoordinates[i] + gap;
596
- const proportionAlong = (pointerLocation - base) / (subtotal - base);
597
- if (pointerLocation < subtotal) {
598
- return i + proportionAlong;
599
- }
600
- }
596
+ }
597
+ export function contentSize(axisCoordinates, gap) {
598
+ const totalCells = axisCoordinates.reduce((sum, size) => sum + size, 0);
599
+ const totalGaps = Math.max(0, axisCoordinates.length - 1) * gap;
600
+ return totalCells + totalGaps;
601
+ }
602
+ export function findCell(pointerLocation, start, size, gap, axisCoordinates) {
603
+ // If outside the axis, then return the ends.
604
+ if (pointerLocation < start) {
605
+ return 0;
606
+ }
607
+ if (pointerLocation >= start + size) {
601
608
  return axisCoordinates.length;
602
609
  }
610
+ let subtotal = start - gap / 2;
611
+ for (let i = 0; i < axisCoordinates.length; i++) {
612
+ const base = subtotal;
613
+ subtotal += axisCoordinates[i] + gap;
614
+ const proportionAlong = (pointerLocation - base) / (subtotal - base);
615
+ if (pointerLocation < subtotal) {
616
+ return i + proportionAlong;
617
+ }
618
+ }
619
+ return axisCoordinates.length;
603
620
  }
604
621
  let uniqueIdIndex = 0;
605
622
  export function generateUniqueId(prefix = 'flexi-') {
@@ -223,28 +223,31 @@ export class InternalFlexiTargetController {
223
223
  * @returns The layout of widgets.
224
224
  */
225
225
  exportLayout() {
226
- const result = [];
227
- // Likely much more information than needed, but we've got it.
228
- for (const widget of this.internalWidgets) {
229
- if (!widget.type) {
230
- console.warn('exportLayout(): widget has no type, it will be skipped.');
231
- continue;
232
- }
233
- const entry = {
234
- type: widget.type,
235
- width: widget.width,
236
- height: widget.height,
237
- x: widget.x,
238
- y: widget.y,
239
- metadata: widget.metadata
240
- };
241
- // Only include id if user provided one
242
- if (widget.userProvidedId) {
243
- entry.id = widget.userProvidedId;
226
+ // Prevent reactive subscriptions onto exportLayout directly - they should use onLayoutChange.
227
+ return untrack(() => {
228
+ const result = [];
229
+ // Likely much more information than needed, but we've got it.
230
+ for (const widget of this.internalWidgets) {
231
+ if (!widget.type) {
232
+ console.warn('exportLayout(): widget has no type, it will be skipped.');
233
+ continue;
234
+ }
235
+ const entry = {
236
+ type: widget.type,
237
+ width: widget.width,
238
+ height: widget.height,
239
+ x: widget.x,
240
+ y: widget.y,
241
+ metadata: widget.metadata
242
+ };
243
+ // Only include id if user provided one
244
+ if (widget.userProvidedId) {
245
+ entry.id = widget.userProvidedId;
246
+ }
247
+ result.push(entry);
244
248
  }
245
- result.push(entry);
246
- }
247
- return result;
249
+ return result;
250
+ });
248
251
  }
249
252
  #createShadow(of, action) {
250
253
  const shadow = new InternalFlexiWidgetController({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "svelte-flexiboards",
3
3
  "licence": "MIT",
4
- "version": "0.4.0",
4
+ "version": "0.4.1",
5
5
  "description": "The headless drag-and-drop toolkit for Svelte.",
6
6
  "scripts": {
7
7
  "dev": "pnpm watch",