svelte-flexiboards 0.4.0 → 0.4.2

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 (35) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +15 -15
  3. package/dist/components/flexi-add.svelte +74 -86
  4. package/dist/components/flexi-add.svelte.d.ts +0 -12
  5. package/dist/components/flexi-announcer.svelte +24 -24
  6. package/dist/components/flexi-board.svelte +58 -58
  7. package/dist/components/flexi-delete.svelte +54 -66
  8. package/dist/components/flexi-grab.svelte +28 -28
  9. package/dist/components/flexi-grid.svelte +27 -27
  10. package/dist/components/flexi-layout-loader.svelte +10 -10
  11. package/dist/components/flexi-portal.svelte +14 -14
  12. package/dist/components/flexi-resize.svelte +30 -30
  13. package/dist/components/flexi-target-loader.svelte +14 -14
  14. package/dist/components/flexi-target.svelte +104 -104
  15. package/dist/components/flexi-widget.svelte +81 -81
  16. package/dist/components/rendered-flexi-widget.svelte +64 -64
  17. package/dist/components/responsive-flexi-board.svelte +83 -83
  18. package/dist/components/widget-transition-placeholder.svelte +12 -12
  19. package/dist/system/board/controller.svelte.d.ts +8 -8
  20. package/dist/system/board/controller.svelte.js +4 -0
  21. package/dist/system/internal-types.d.ts +109 -0
  22. package/dist/system/internal-types.js +1 -0
  23. package/dist/system/misc/adder.svelte.d.ts +2 -2
  24. package/dist/system/portal.d.ts +3 -3
  25. package/dist/system/shared/event-bus.d.ts +15 -14
  26. package/dist/system/shared/utils.svelte.d.ts +6 -3
  27. package/dist/system/shared/utils.svelte.js +49 -32
  28. package/dist/system/target/controller.svelte.d.ts +12 -11
  29. package/dist/system/target/controller.svelte.js +25 -22
  30. package/dist/system/target/types.d.ts +2 -2
  31. package/dist/system/types.d.ts +26 -27
  32. package/dist/system/widget/controller.svelte.d.ts +6 -6
  33. package/dist/system/widget/controller.svelte.js +1 -2
  34. package/dist/system/widget/triggers.svelte.js +9 -0
  35. package/package.json +60 -61
@@ -1,83 +1,83 @@
1
- <script module lang="ts">
2
- import { onDestroy, type Snippet } from 'svelte';
3
- import type { FlexiCommonProps } from '../system/types.js';
4
- import type { ResponsiveFlexiBoardController } from '../system/responsive/base.svelte.js';
5
- import type { ResponsiveFlexiBoardConfiguration } from '../system/responsive/types.js';
6
-
7
- export type BreakpointSnippetParams = { currentBreakpoint: string };
8
-
9
- export type ResponsiveFlexiBoardProps = FlexiCommonProps<ResponsiveFlexiBoardController> & {
10
- config?: ResponsiveFlexiBoardConfiguration;
11
- /**
12
- * Snippet for large breakpoint (no params - breakpoint is implicit).
13
- */
14
- lg?: Snippet;
15
- /**
16
- * Snippet for medium breakpoint (no params - breakpoint is implicit).
17
- */
18
- md?: Snippet;
19
- /**
20
- * Snippet for small breakpoint (no params - breakpoint is implicit).
21
- */
22
- sm?: Snippet;
23
- /**
24
- * Snippet for extra-small breakpoint (no params - breakpoint is implicit).
25
- */
26
- xs?: Snippet;
27
- /**
28
- * Children snippet used as fallback when no specific breakpoint snippet matches.
29
- * Receives `{ currentBreakpoint: string }` as a parameter.
30
- */
31
- children?: Snippet<[BreakpointSnippetParams]>;
32
- };
33
- </script>
34
-
35
- <script lang="ts">
36
- import { responsiveflexiboard } from '../system/responsive/index.js';
37
-
38
- let {
39
- controller = $bindable(),
40
- onfirstcreate,
41
- config,
42
- lg,
43
- md,
44
- sm,
45
- xs,
46
- children
47
- }: ResponsiveFlexiBoardProps = $props();
48
-
49
- const board = responsiveflexiboard({ config });
50
- controller = board;
51
-
52
- // Load layouts immediately so child FlexiBoards can access them
53
- board.oninitialloadcomplete();
54
-
55
- onfirstcreate?.(board);
56
-
57
- // Map breakpoint names to snippets (without params)
58
- const snippets: Record<string, Snippet | undefined> = $derived({
59
- lg,
60
- md,
61
- sm,
62
- xs
63
- });
64
-
65
- // Cleanup when component is destroyed
66
- onDestroy(() => {
67
- board.destroy();
68
- });
69
- </script>
70
-
71
- <!--
72
- Key on currentBreakpoint to force re-render when breakpoint changes.
73
- This ensures the board is re-created with the correct breakpoint context.
74
- -->
75
- {#key board.currentBreakpoint}
76
- {#if snippets[board.currentBreakpoint]}
77
- <!-- Specific breakpoint snippet (no params) -->
78
- {@render snippets[board.currentBreakpoint]!()}
79
- {:else if children}
80
- <!-- Children snippet receives { currentBreakpoint } -->
81
- {@render children({ currentBreakpoint: board.currentBreakpoint })}
82
- {/if}
83
- {/key}
1
+ <script module lang="ts">
2
+ import { onDestroy, type Snippet } from 'svelte';
3
+ import type { FlexiCommonProps } from '../system/types.js';
4
+ import type { ResponsiveFlexiBoardController } from '../system/responsive/base.svelte.js';
5
+ import type { ResponsiveFlexiBoardConfiguration } from '../system/responsive/types.js';
6
+
7
+ export type BreakpointSnippetParams = { currentBreakpoint: string };
8
+
9
+ export type ResponsiveFlexiBoardProps = FlexiCommonProps<ResponsiveFlexiBoardController> & {
10
+ config?: ResponsiveFlexiBoardConfiguration;
11
+ /**
12
+ * Snippet for large breakpoint (no params - breakpoint is implicit).
13
+ */
14
+ lg?: Snippet;
15
+ /**
16
+ * Snippet for medium breakpoint (no params - breakpoint is implicit).
17
+ */
18
+ md?: Snippet;
19
+ /**
20
+ * Snippet for small breakpoint (no params - breakpoint is implicit).
21
+ */
22
+ sm?: Snippet;
23
+ /**
24
+ * Snippet for extra-small breakpoint (no params - breakpoint is implicit).
25
+ */
26
+ xs?: Snippet;
27
+ /**
28
+ * Children snippet used as fallback when no specific breakpoint snippet matches.
29
+ * Receives `{ currentBreakpoint: string }` as a parameter.
30
+ */
31
+ children?: Snippet<[BreakpointSnippetParams]>;
32
+ };
33
+ </script>
34
+
35
+ <script lang="ts">
36
+ import { responsiveflexiboard } from '../system/responsive/index.js';
37
+
38
+ let {
39
+ controller = $bindable(),
40
+ onfirstcreate,
41
+ config,
42
+ lg,
43
+ md,
44
+ sm,
45
+ xs,
46
+ children
47
+ }: ResponsiveFlexiBoardProps = $props();
48
+
49
+ const board = responsiveflexiboard({ config });
50
+ controller = board;
51
+
52
+ // Load layouts immediately so child FlexiBoards can access them
53
+ board.oninitialloadcomplete();
54
+
55
+ onfirstcreate?.(board);
56
+
57
+ // Map breakpoint names to snippets (without params)
58
+ const snippets: Record<string, Snippet | undefined> = $derived({
59
+ lg,
60
+ md,
61
+ sm,
62
+ xs
63
+ });
64
+
65
+ // Cleanup when component is destroyed
66
+ onDestroy(() => {
67
+ board.destroy();
68
+ });
69
+ </script>
70
+
71
+ <!--
72
+ Key on currentBreakpoint to force re-render when breakpoint changes.
73
+ This ensures the board is re-created with the correct breakpoint context.
74
+ -->
75
+ {#key board.currentBreakpoint}
76
+ {#if snippets[board.currentBreakpoint]}
77
+ <!-- Specific breakpoint snippet (no params) -->
78
+ {@render snippets[board.currentBreakpoint]!()}
79
+ {:else if children}
80
+ <!-- Children snippet receives { currentBreakpoint } -->
81
+ {@render children({ currentBreakpoint: board.currentBreakpoint })}
82
+ {/if}
83
+ {/key}
@@ -1,12 +1,12 @@
1
- <script lang="ts">
2
- import { getFlexiwidgetInterpolatorCtx } from '../system/widget/index.js';
3
- import { onMount } from 'svelte';
4
-
5
- const interpolator = getFlexiwidgetInterpolatorCtx();
6
-
7
- let ref: HTMLElement;
8
-
9
- onMount(() => interpolator.onPlaceholderMount(ref));
10
- </script>
11
-
12
- <div style={interpolator.placeholderStyle} bind:this={ref}></div>
1
+ <script lang="ts">
2
+ import { getFlexiwidgetInterpolatorCtx } from '../system/widget/index.js';
3
+ import { onMount } from 'svelte';
4
+
5
+ const interpolator = getFlexiwidgetInterpolatorCtx();
6
+
7
+ let ref: HTMLElement;
8
+
9
+ onMount(() => interpolator.onPlaceholderMount(ref));
10
+ </script>
11
+
12
+ <div style={interpolator.placeholderStyle} bind:this={ref}></div>
@@ -3,7 +3,7 @@ import type { AriaPoliteness, FlexiAnnouncerController } from '../announcer.svel
3
3
  import type { FlexiPortalController } from '../portal.js';
4
4
  import { InternalFlexiTargetController } from '../target/controller.svelte.js';
5
5
  import type { FlexiTargetPartialConfiguration } from '../target/types.js';
6
- import type { TargetEvent, WidgetAction, WidgetEvent, WidgetGrabbedEvent, WidgetResizingEvent } from '../types.js';
6
+ import type { InternalTargetEvent, InternalWidgetAction, InternalWidgetEvent, InternalWidgetGrabbedEvent, InternalWidgetResizingEvent } from '../internal-types.js';
7
7
  import type { FlexiBoardController } from './base.svelte.js';
8
8
  import type { FlexiBoardConfiguration, FlexiRegistryEntry, FlexiLayout } from './types.js';
9
9
  import type { InternalFlexiWidgetController } from '../widget/controller.svelte.js';
@@ -22,14 +22,14 @@ export declare class InternalFlexiBoardController implements FlexiBoardControlle
22
22
  get ref(): HTMLElement | null;
23
23
  set ref(ref: HTMLElement | null);
24
24
  createTarget(config?: FlexiTargetPartialConfiguration, key?: string): InternalFlexiTargetController;
25
- onPointerEnterTarget(event: TargetEvent): void;
26
- onPointerLeaveTarget(event: TargetEvent): void;
25
+ onPointerEnterTarget(event: InternalTargetEvent): void;
26
+ onPointerLeaveTarget(event: InternalTargetEvent): void;
27
27
  onenterdeleter(): void;
28
28
  onleavedeleter(): void;
29
- onWidgetGrabbed(event: WidgetGrabbedEvent): void;
30
- onWidgetResizing(event: WidgetResizingEvent): void;
31
- handleWidgetRelease(event: WidgetEvent): void;
32
- handleWidgetCancel(event: WidgetEvent): void;
29
+ onWidgetGrabbed(event: InternalWidgetGrabbedEvent): void;
30
+ onWidgetResizing(event: InternalWidgetResizingEvent): void;
31
+ handleWidgetRelease(event: InternalWidgetEvent): void;
32
+ handleWidgetCancel(event: InternalWidgetEvent): void;
33
33
  attachAnnouncer(announcer: FlexiAnnouncerController): void;
34
34
  announce(message: string, politeness?: AriaPoliteness): void;
35
35
  oninitialloadcomplete(): void;
@@ -54,7 +54,7 @@ export declare class InternalFlexiBoardController implements FlexiBoardControlle
54
54
  * @param to The target to move the widget to.
55
55
  */
56
56
  moveWidget(widget: InternalFlexiWidgetController, from: InternalFlexiTargetController | undefined, to: InternalFlexiTargetController): boolean;
57
- get currentWidgetAction(): WidgetAction | null;
57
+ get currentWidgetAction(): InternalWidgetAction | null;
58
58
  /**
59
59
  * Returns the parent responsive controller if this board is within a ResponsiveFlexiBoard.
60
60
  */
@@ -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);
@@ -0,0 +1,109 @@
1
+ import type { FlexiLayout } from './board/types.js';
2
+ import type { InternalFlexiBoardController } from './board/controller.svelte.js';
3
+ import type { InternalFlexiAddController } from './misc/adder.svelte.js';
4
+ import type { InternalResponsiveFlexiBoardController } from './responsive/controller.svelte.js';
5
+ import type { InternalFlexiTargetController } from './target/controller.svelte.js';
6
+ import type { InternalFlexiWidgetController } from './widget/controller.svelte.js';
7
+ import type { Position } from './types.js';
8
+ export type InternalWidgetGrabAction = {
9
+ action: 'grab';
10
+ widget: InternalFlexiWidgetController;
11
+ offsetX: number;
12
+ offsetY: number;
13
+ capturedHeightPx: number;
14
+ capturedWidthPx: number;
15
+ };
16
+ export type InternalWidgetResizeAction = {
17
+ action: 'resize';
18
+ widget: InternalFlexiWidgetController;
19
+ offsetX: number;
20
+ offsetY: number;
21
+ left: number;
22
+ top: number;
23
+ capturedHeightPx: number;
24
+ capturedWidthPx: number;
25
+ initialHeightUnits: number;
26
+ initialWidthUnits: number;
27
+ };
28
+ export type InternalWidgetAction = InternalWidgetGrabAction | InternalWidgetResizeAction;
29
+ export type InternalWidgetGrabbedParams = {
30
+ widget: InternalFlexiWidgetController;
31
+ ref: HTMLElement;
32
+ xOffset: number;
33
+ yOffset: number;
34
+ clientX: number;
35
+ clientY: number;
36
+ capturedHeight: number;
37
+ capturedWidth: number;
38
+ };
39
+ export type InternalWidgetStartResizeParams = {
40
+ widget: InternalFlexiWidgetController;
41
+ xOffset: number;
42
+ yOffset: number;
43
+ left: number;
44
+ top: number;
45
+ heightPx: number;
46
+ widthPx: number;
47
+ };
48
+ export type InternalAdderWidgetReadyEvent = {
49
+ adder: InternalFlexiAddController;
50
+ widget: InternalFlexiWidgetController;
51
+ };
52
+ export type InternalWidgetEvent = {
53
+ target?: InternalFlexiTargetController;
54
+ board: InternalFlexiBoardController;
55
+ widget: InternalFlexiWidgetController;
56
+ };
57
+ export type InternalWidgetGrabbedEvent = InternalWidgetEvent & {
58
+ clientX: number;
59
+ clientY: number;
60
+ xOffset: number;
61
+ yOffset: number;
62
+ capturedHeightPx: number;
63
+ capturedWidthPx: number;
64
+ };
65
+ export type InternalWidgetResizingEvent = InternalWidgetEvent & {
66
+ target: InternalFlexiTargetController;
67
+ offsetX: number;
68
+ offsetY: number;
69
+ clientX: number;
70
+ clientY: number;
71
+ left: number;
72
+ top: number;
73
+ capturedHeightPx: number;
74
+ capturedWidthPx: number;
75
+ };
76
+ export type InternalWidgetDroppedEvent = {
77
+ widget: InternalFlexiWidgetController;
78
+ board: InternalFlexiBoardController;
79
+ oldTarget?: InternalFlexiTargetController;
80
+ newTarget?: InternalFlexiTargetController;
81
+ };
82
+ export type InternalWidgetStartResizeEvent = InternalWidgetStartResizeParams & {
83
+ target: InternalFlexiTargetController;
84
+ };
85
+ export type InternalWidgetOverEvent = {
86
+ widget: InternalFlexiWidgetController;
87
+ mousePosition: Position;
88
+ };
89
+ export type InternalWidgetOutEvent = {
90
+ widget: InternalFlexiWidgetController;
91
+ };
92
+ export type InternalTargetEvent = {
93
+ board: InternalFlexiBoardController;
94
+ target: InternalFlexiTargetController;
95
+ };
96
+ export type InternalGrabbedWidgetMouseEvent = {
97
+ widget: InternalFlexiWidgetController;
98
+ };
99
+ export type InternalHoveredTargetEvent = {
100
+ target: InternalFlexiTargetController;
101
+ };
102
+ export type InternalBoardLayoutChangeEvent = {
103
+ board: InternalFlexiBoardController;
104
+ layout: FlexiLayout;
105
+ breakpoint?: string;
106
+ };
107
+ export type InternalResponsiveLayoutImportEvent = {
108
+ responsiveController: InternalResponsiveFlexiBoardController;
109
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import type { ClassValue } from 'svelte/elements';
2
2
  import type { InternalFlexiBoardController } from '../board/controller.svelte.js';
3
- import type { AdderWidgetReadyEvent } from '../types.js';
3
+ import type { InternalAdderWidgetReadyEvent } from '../internal-types.js';
4
4
  import { InternalFlexiWidgetController } from '../widget/controller.svelte.js';
5
5
  import type { FlexiWidgetConfiguration } from '../widget/index.js';
6
6
  export type FlexiAddWidgetFn = () => AdderWidgetConfiguration | null;
@@ -29,7 +29,7 @@ export declare class InternalFlexiAddController implements FlexiAddController {
29
29
  constructor(provider: InternalFlexiBoardController, addWidgetFn: FlexiAddWidgetFn);
30
30
  onpointerdown(event: PointerEvent): void;
31
31
  onkeydown(event: KeyboardEvent): void;
32
- onWidgetReady(event: AdderWidgetReadyEvent): void;
32
+ onWidgetReady(event: InternalAdderWidgetReadyEvent): void;
33
33
  onWidgetDragInComplete(event: {
34
34
  widget: InternalFlexiWidgetController;
35
35
  }): void;
@@ -1,4 +1,4 @@
1
- import type { WidgetGrabbedEvent, WidgetEvent } from './types.js';
1
+ import type { InternalWidgetEvent, InternalWidgetGrabbedEvent } from './internal-types.js';
2
2
  import type { FlexiWidgetController } from './widget/index.js';
3
3
  /**
4
4
  * GrabbedPortal manages a single container in the DOM where grabbed/resizing
@@ -8,8 +8,8 @@ export declare class FlexiPortalController {
8
8
  #private;
9
9
  constructor();
10
10
  createPortal(): void;
11
- onWidgetGrabbed(event: WidgetGrabbedEvent): void;
12
- onWidgetRelease(event: WidgetEvent): void;
11
+ onWidgetGrabbed(event: InternalWidgetGrabbedEvent): void;
12
+ onWidgetRelease(event: InternalWidgetEvent): void;
13
13
  /**
14
14
  * Moves a widget's DOM element to the portal container
15
15
  */
@@ -1,19 +1,20 @@
1
- import type { AdderWidgetReadyEvent, BoardLayoutChangeEvent, PointerMovedEvent, ResponsiveLayoutImportEvent, TargetEvent, WidgetDroppedEvent, WidgetEvent, WidgetGrabbedEvent, WidgetResizingEvent } from '../types.js';
1
+ import type { InternalAdderWidgetReadyEvent, InternalBoardLayoutChangeEvent, InternalResponsiveLayoutImportEvent, InternalTargetEvent, InternalWidgetDroppedEvent, InternalWidgetEvent, InternalWidgetGrabbedEvent, InternalWidgetResizingEvent } from '../internal-types.js';
2
+ import type { PointerMovedEvent } from '../types.js';
2
3
  export interface EventMap {
3
- 'widget:grabbed': WidgetGrabbedEvent;
4
- 'widget:resizing': WidgetResizingEvent;
5
- 'widget:release': WidgetEvent;
6
- 'widget:cancel': WidgetEvent;
7
- 'widget:dropped': WidgetDroppedEvent;
8
- 'widget:delete': WidgetEvent;
9
- 'target:pointerenter': TargetEvent;
10
- 'target:pointerleave': TargetEvent;
11
- 'widget:entertarget': WidgetEvent;
12
- 'widget:leavetarget': WidgetEvent;
13
- 'adder:widgetready': AdderWidgetReadyEvent;
4
+ 'widget:grabbed': InternalWidgetGrabbedEvent;
5
+ 'widget:resizing': InternalWidgetResizingEvent;
6
+ 'widget:release': InternalWidgetEvent;
7
+ 'widget:cancel': InternalWidgetEvent;
8
+ 'widget:dropped': InternalWidgetDroppedEvent;
9
+ 'widget:delete': InternalWidgetEvent;
10
+ 'target:pointerenter': InternalTargetEvent;
11
+ 'target:pointerleave': InternalTargetEvent;
12
+ 'widget:entertarget': InternalWidgetEvent;
13
+ 'widget:leavetarget': InternalWidgetEvent;
14
+ 'adder:widgetready': InternalAdderWidgetReadyEvent;
14
15
  'pointer:moved': PointerMovedEvent;
15
- 'board:layoutchange': BoardLayoutChangeEvent;
16
- 'responsive:layoutimport': ResponsiveLayoutImportEvent;
16
+ 'board:layoutchange': InternalBoardLayoutChangeEvent;
17
+ 'responsive:layoutimport': InternalResponsiveLayoutImportEvent;
17
18
  }
18
19
  type EventListener<T> = (data: T) => void;
19
20
  export declare class FlexiEventBus {
@@ -1,4 +1,5 @@
1
- import type { Position, ProxiedValue, WidgetEvent } from '../types.js';
1
+ import type { InternalWidgetEvent } from '../internal-types.js';
2
+ import type { Position, ProxiedValue } from '../types.js';
2
3
  import type { FlexiGrid } from '../grid/base.svelte.js';
3
4
  import type { FlexiTargetConfiguration } from '../target/types.js';
4
5
  /**
@@ -34,8 +35,8 @@ export declare class AutoScrollService {
34
35
  #private;
35
36
  shouldAutoScroll: boolean;
36
37
  constructor(ref: ProxiedValue<HTMLElement | null>);
37
- startAutoScroll(event: WidgetEvent): void;
38
- stopAutoScroll(event: WidgetEvent): void;
38
+ startAutoScroll(event: InternalWidgetEvent): void;
39
+ stopAutoScroll(event: InternalWidgetEvent): void;
39
40
  get ref(): HTMLElement | null;
40
41
  /**
41
42
  * Manually stop continuous scrolling. Useful for external control.
@@ -60,6 +61,8 @@ export declare class GridDimensionTracker {
60
61
  refreshScrollListeners(): void;
61
62
  getCellFromPointerPosition(clientX: number, clientY: number): CellPosition | null;
62
63
  }
64
+ export declare function contentSize(axisCoordinates: number[], gap: number): number;
65
+ export declare function findCell(pointerLocation: number, start: number, size: number, gap: number, axisCoordinates: number[]): number;
63
66
  export declare function generateUniqueId(prefix?: string): string;
64
67
  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
68
  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,48 +578,61 @@ 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-') {
606
623
  return prefix + uniqueIdIndex++;
607
624
  }
608
625
  /* Adapted from TailwindCSS sr-only */
609
- export const assistiveTextStyle = `
610
- position: absolute;
611
- width: 1px;
612
- height: 1px;
613
- padding: 0;
614
- margin: -1px;
615
- overflow: hidden;
616
- clip: rect(0, 0, 0, 0);
617
- white-space: nowrap;
618
- border-width: 0;
626
+ export const assistiveTextStyle = `
627
+ position: absolute;
628
+ width: 1px;
629
+ height: 1px;
630
+ padding: 0;
631
+ margin: -1px;
632
+ overflow: hidden;
633
+ clip: rect(0, 0, 0, 0);
634
+ white-space: nowrap;
635
+ border-width: 0;
619
636
  `;
620
637
  export function getElementMidpoint(element) {
621
638
  const rect = element.getBoundingClientRect();
@@ -1,5 +1,6 @@
1
1
  import { FlexiGrid } from '../grid/index.js';
2
- import type { MouseGridCellMoveEvent, TargetEvent, WidgetDroppedEvent, WidgetEvent, WidgetGrabbedEvent, WidgetResizingEvent } from '../types.js';
2
+ import type { InternalTargetEvent, InternalWidgetDroppedEvent, InternalWidgetEvent, InternalWidgetGrabbedEvent, InternalWidgetResizingEvent } from '../internal-types.js';
3
+ import type { MouseGridCellMoveEvent } from '../types.js';
3
4
  import { FlexiWidgetController } from '../widget/base.svelte.js';
4
5
  import { InternalFlexiWidgetController } from '../widget/controller.svelte.js';
5
6
  import type { FlexiWidgetConfiguration, FlexiWidgetDefaults } from '../widget/types.js';
@@ -19,7 +20,7 @@ export declare class InternalFlexiTargetController implements FlexiTargetControl
19
20
  createGrid(): FlexiGrid;
20
21
  createWidget(config: FlexiWidgetConfiguration): InternalFlexiWidgetController | undefined;
21
22
  registerWidget(config: FlexiWidgetConfiguration, onCreated?: (widget: FlexiWidgetController) => void): void;
22
- onWidgetDelete(event: WidgetEvent): void;
23
+ onWidgetDelete(event: InternalWidgetEvent): void;
23
24
  /**
24
25
  * Deletes the given widget from this target, if it exists.
25
26
  * @returns Whether the widget was deleted.
@@ -36,8 +37,8 @@ export declare class InternalFlexiTargetController implements FlexiTargetControl
36
37
  * @returns The layout of widgets.
37
38
  */
38
39
  exportLayout(): FlexiWidgetLayoutEntry[];
39
- onPointerEnterTarget(event: TargetEvent): void;
40
- onPointerLeaveTarget(event: TargetEvent): void;
40
+ onPointerEnterTarget(event: InternalTargetEvent): void;
41
+ onPointerLeaveTarget(event: InternalTargetEvent): void;
41
42
  restorePreGrabSnapshot(): void;
42
43
  forgetPreGrabSnapshot(): void;
43
44
  hasPreGrabSnapshot(): boolean;
@@ -45,13 +46,13 @@ export declare class InternalFlexiTargetController implements FlexiTargetControl
45
46
  cancelDrop(): void;
46
47
  tryDropWidget(widget: InternalFlexiWidgetController): boolean;
47
48
  onmousegridcellmove(event: MouseGridCellMoveEvent): void;
48
- onWidgetGrabbed(event: WidgetGrabbedEvent): void;
49
- onWidgetResizing(event: WidgetResizingEvent): void;
50
- onWidgetCancel(event: WidgetEvent): void;
51
- onWidgetRelease(event: WidgetEvent): void;
52
- onWidgetDropped(event: WidgetDroppedEvent): void;
53
- onWidgetEnterTarget(event: WidgetEvent): void;
54
- onWidgetLeaveTarget(event: WidgetEvent): void;
49
+ onWidgetGrabbed(event: InternalWidgetGrabbedEvent): void;
50
+ onWidgetResizing(event: InternalWidgetResizingEvent): void;
51
+ onWidgetCancel(event: InternalWidgetEvent): void;
52
+ onWidgetRelease(event: InternalWidgetEvent): void;
53
+ onWidgetDropped(event: InternalWidgetDroppedEvent): void;
54
+ onWidgetEnterTarget(event: InternalWidgetEvent): void;
55
+ onWidgetLeaveTarget(event: InternalWidgetEvent): void;
55
56
  oninitialloadcomplete(): void;
56
57
  /**
57
58
  * Whether the target is currently being hovered over by the mouse.