@featherk/composables 0.13.7 → 0.13.9

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.
@@ -29,6 +29,10 @@ export interface WindowSettings {
29
29
  height?: number;
30
30
  left?: number;
31
31
  top?: number;
32
+ /** Minimum resize width in px. Unset/`null` falls back to Kendo's default (120). */
33
+ minWidth?: number | null;
34
+ /** Minimum resize height in px. Unset/`null` falls back to Kendo's default (100). */
35
+ minHeight?: number | null;
32
36
  }
33
37
  export declare const WINDOW_SETTINGS_DEFAULTS: WindowSettings;
34
38
  export interface UseWindowOptions {
@@ -63,6 +67,8 @@ export interface WindowBindableProps {
63
67
  initialHeight: number;
64
68
  width?: number;
65
69
  height?: number;
70
+ minWidth: number;
71
+ minHeight: number;
66
72
  stage: WindowStage;
67
73
  windowClass: string;
68
74
  }
@@ -4,7 +4,7 @@
4
4
 
5
5
  > Wraps Kendo UI for Vue's `Window` component - not the browser `window` object. If you were expecting something like VueUse's `useWindowSize`/`useWindowScroll`, this is unrelated to those.
6
6
 
7
- Composable for a zero/low-config Kendo `Window`: viewport-clamped dragging, resize tracking, stage transitions, and the pointerdown-capture resize/text-selection workaround are all handled internally. Every setting has a sensible default, so `useWindow()` with no arguments is a complete, working Window.
7
+ Composable for a zero/low-config Kendo `Window`: viewport-clamped dragging and resizing, stage transitions, and the pointerdown-capture resize/text-selection workaround are all handled internally. Every setting has a sensible default, so `useWindow()` with no arguments is a complete, working Window.
8
8
 
9
9
  ## Quick Start
10
10
 
@@ -67,6 +67,23 @@ A partially-populated ref is fine - any field `myLoadedSettings` doesn't have fa
67
67
 
68
68
  Set `settings.initialLeft` and/or `settings.initialTop` to override the default 16px right/top inset on the first open and whenever `resetPosition` is true. Each coordinate can be overridden independently; an omitted coordinate keeps its computed inset default.
69
69
 
70
+ ## Resizing Bounds
71
+
72
+ Resizing is clamped to the viewport (the scrolled document viewport when `settings.fixed` is `false`):
73
+
74
+ - The dragged edge stops at the viewport edge; the opposite edge stays anchored, so dragging the top or left edge past the viewport never grows the Window down or right.
75
+ - An edge that is already outside the viewport (for example, a bottom edge dragged below the fold) is never snapped back - it just cannot be pushed further out.
76
+ - The Window cannot shrink below `settings.minWidth`/`settings.minHeight`. Both are optional; unset or `null` falls back to Kendo's defaults (120px/100px).
77
+ - Keyboard resizing (Ctrl+Arrow) follows the same viewport and minimum-size limits.
78
+
79
+ `settings.left`/`top`/`width`/`height` and the `onResize` payload always hold these clamped values, not Kendo's raw resize geometry.
80
+
81
+ ```ts
82
+ const win = useWindow({
83
+ settings: { minWidth: 320, minHeight: 200 },
84
+ });
85
+ ```
86
+
70
87
  ## Extending Window Behavior
71
88
 
72
89
  Use the `UseWindowOptions` callbacks when application code needs to react to Window behavior. These callbacks are extension hooks: they run after `useWindow` completes its internal handling, and they do not replace Kendo's event handlers.
@@ -74,7 +91,7 @@ Use the `UseWindowOptions` callbacks when application code needs to react to Win
74
91
  This applies to every callback option:
75
92
 
76
93
  - `onMove` runs after viewport clamping and position updates.
77
- - `onResize` runs after size/position updates and resize-state tracking.
94
+ - `onResize` runs after viewport clamping, size/position updates, and resize-state tracking.
78
95
  - `onStageChange` runs after a valid controlled stage transition.
79
96
  - `onOpen` runs after `open()` has shown and sized the Window.
80
97
  - `onClose` runs after `close()` has reset the stage and hidden the Window.
@@ -99,7 +116,7 @@ the titlebar toggles between `DEFAULT` and `FULLSCREEN`. Set `titlebarDoubleClic
99
116
  disable both transitions. This option is independent of `settings.maximizeButton`; disabling the
100
117
  maximize button does not automatically disable titlebar double-click behavior.
101
118
 
102
- Keep binding `v-on="events"` on `<Window>` so viewport clamping, resize tracking, stage handling, and Kendo workarounds remain active:
119
+ Keep binding `v-on="events"` on `<Window>` so viewport clamping (drag and resize), stage handling, and Kendo workarounds remain active:
103
120
 
104
121
  ```vue
105
122
  <Window
@@ -122,7 +139,7 @@ Do not replace entries on `events` unless you intentionally opt out of the corre
122
139
  | `initialStage` | `WindowStage` | Initial `stage` value. Defaults to `"DEFAULT"`. |
123
140
  | `titlebarDoubleClick` | `MaybeRefOrGetter<boolean>` | Enables titlebar double-click stage changes. Defaults to `true`; pass `false` or a reactive ref/getter returning `false` to disable both maximize and restore transitions. |
124
141
  | `onMove` | `(payload) => void` | Called after internal move handling. |
125
- | `onResize` | `(payload) => void` | Called after internal resize handling. |
142
+ | `onResize` | `(payload) => void` | Called after internal resize handling, with the viewport-clamped `left`/`top`/`width`/`height`. |
126
143
  | `onStageChange` | `(stage) => void` | Called after a valid stage transition. |
127
144
  | `onOpen` | `() => void` | Called after `open()` finishes sizing the Window. |
128
145
  | `onClose` | `() => void` | Called after `close()` hides the Window. |
@@ -143,7 +160,7 @@ Do not replace entries on `events` unless you intentionally opt out of the corre
143
160
 
144
161
  ## Inert Props
145
162
 
146
- `props` only contains Kendo `Window` props that actually work: `title`, `draggable`, `resizable`, `left`, `top`, `initialLeft`, `initialTop`, `initialWidth`, `initialHeight`, `width`, `height`, `stage`, and `windowClass`. `settings.closable`, `settings.minimizeButton`, and `settings.maximizeButton` are deliberately **not** included in `props`.
163
+ `props` only contains Kendo `Window` props that actually work: `title`, `draggable`, `resizable`, `left`, `top`, `initialLeft`, `initialTop`, `initialWidth`, `initialHeight`, `width`, `height`, `minWidth`, `minHeight`, `stage`, and `windowClass`. `settings.closable`, `settings.minimizeButton`, and `settings.maximizeButton` are deliberately **not** included in `props`.
147
164
 
148
165
  If you hand-wire raw Kendo props like `closeButton`/`minimizeButton`/`maximizeButton` directly on `<Window>` alongside `v-bind="props"`, they are **inert** - they will not hide the corresponding titlebar button. `useWindow` only ever suppresses those buttons via CSS (`fk-window-hide-minimize`/`fk-window-hide-maximize`/`fk-window-hide-close` classes folded into `windowClass`), never through Kendo's own button props. Toggle `settings.minimizeButton`/`settings.maximizeButton`/`settings.closable` instead.
149
166
 
@@ -151,6 +168,36 @@ If you hand-wire raw Kendo props like `closeButton`/`minimizeButton`/`maximizeBu
151
168
 
152
169
  - **Titlebar button props are non-functional.** Kendo's `Window` types `minimizeButton`/`maximizeButton`/`closeButton`/`restoreButton` as `[String, Function]`, but the child `WindowTitlebar` that actually renders them types the same props `[String, Function, Object, Boolean]` and renders each button unconditionally regardless of value. There is no supported way to hide these buttons through Kendo's own props - see "Inert Props" above for the CSS-class workaround `useWindow` uses instead.
153
170
  - **Resize-handle drag vs. text-selection race.** Kendo Window's resize handles use Kendo's own draggable implementation, which does not prevent the native browser default until after pointer movement begins. If text inside the Window is already selected, native selection-dragging can compete with a resize started from an inward-overlapping handle. `useWindow` prevents this internally with a capture-phase `pointerdown` listener scoped to `.k-resize-handle` elements - see [docs/design/composable-native-event-listener-tactic.md](https://github.com/NantHealth/featherk/blob/integration/docs/design/composable-native-event-listener-tactic.md) for the general pattern. This is fully internal; no `@pointerdown.capture` binding is needed (or exposed) in consumer templates.
171
+ - **Resize geometry is unbounded and self-compounding.** See "Kendo Window Resize Math Defect" below.
172
+
173
+ ### Kendo Window Resize Math Defect
174
+
175
+ Observed in `@progress/kendo-vue-dialogs` 6.4.1 (`Window.mjs`, `handleResize`). Because of this, `useWindow` computes resize geometry itself rather than trusting the values Kendo emits on `resize`.
176
+
177
+ **Symptoms (without the workaround)**
178
+
179
+ - Dragging the right or bottom edge past the browser window keeps growing the Window indefinitely.
180
+ - After that, the left edge can be dragged offscreen too, leaving the whole Window offscreen and unrecoverable.
181
+ - Dragging the top edge up past the viewport makes the Window grow **down**; dragging the left edge past the viewport makes it grow **right**. Corner handles show both behaviors.
182
+
183
+ **Root cause**
184
+
185
+ - Kendo sets the dragged edge to the pointer's absolute `pageX`/`pageY` with no viewport bounds. Pointer capture keeps reporting coordinates beyond the browser window, so right/bottom edges follow indefinitely.
186
+ - For `n`/`w` handles Kendo sets `top = pageY` (negative past the viewport) and adds `computedTop - pageY` to the height. `computedTop` is `Math.max(top, 0)`, so the Window renders at `0` while the height grows again on every pointer move - growth leaks out the opposite edge. Same for `left`/width.
187
+ - The emitted `left`/`top`/`width`/`height` come from Kendo's internal state, not the controlled props: values for the axis not being resized can be stale (including unclamped values from a prior drag).
188
+ - Kendo mixes coordinate spaces: `pageX`/`pageY` are document coordinates, while a `position: fixed` Window's `top`/`left` are viewport coordinates, so resizing is off by the scroll offset on a scrolled page.
189
+ - Kendo reads `props.top || currentTop`, so a controlled `top`/`left` of `0` is treated as unset and falls back to internal state.
190
+
191
+ **Workaround in `useWindow`**
192
+
193
+ - The existing capture-phase `pointerdown` listener on `.k-resize-handle` also records the handle direction (from its `k-resize-<dir>` class), the starting rect, and the starting pointer position, then tracks the pointer with a capture-phase `window` `pointermove` listener.
194
+ - On each Kendo `resize` event, geometry is computed from the pointer delta and clamped per edge (viewport bounds, anchored opposite edge, `minWidth`/`minHeight`); Kendo's emitted geometry is ignored. Kendo's event still drives timing (`drag`/`end`).
195
+ - Keyboard resizing (Ctrl+Arrow) has no pointer, so it applies the change in Kendo's emitted size as a delta to the clamped size.
196
+ - See "Resizing Bounds" above for the resulting behavior.
197
+
198
+ **Removal criteria**
199
+
200
+ Revisit when upgrading `@progress/kendo-vue-dialogs`. The workaround can be dropped once Kendo's `handleResize` bounds edges to the viewport, anchors the opposite edge at the viewport, and emits geometry consistent with the controlled props. The `useWindow` resize tests (`useWindow.test.ts`) describe the expected behavior and can be used to verify.
154
201
 
155
202
  ## Styling
156
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featherk/composables",
3
- "version": "0.13.7",
3
+ "version": "0.13.9",
4
4
  "main": "dist/featherk-composables.umd.js",
5
5
  "module": "dist/featherk-composables.es.js",
6
6
  "types": "dist/index.d.ts",