@surdeddd/wmkit 0.2.0 → 0.4.0

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 (61) hide show
  1. package/README.md +80 -16
  2. package/README.ru.md +33 -9
  3. package/dist/angular.cjs +53 -0
  4. package/dist/angular.cjs.map +1 -0
  5. package/dist/angular.d.cts +15 -0
  6. package/dist/angular.d.ts +15 -0
  7. package/dist/angular.js +48 -0
  8. package/dist/angular.js.map +1 -0
  9. package/dist/{binder-BY-saDUB.d.cts → binder-B2A2IZGh.d.ts} +19 -2
  10. package/dist/{binder-D9UEE1SM.d.ts → binder-Dl07SXIM.d.cts} +19 -2
  11. package/dist/{chunk-KQCVZ2W7.cjs → chunk-3KZZOLHW.cjs} +691 -156
  12. package/dist/chunk-3KZZOLHW.cjs.map +1 -0
  13. package/dist/{chunk-2TVOEDRG.js → chunk-FPZDAKNP.js} +689 -157
  14. package/dist/chunk-FPZDAKNP.js.map +1 -0
  15. package/dist/index.cjs +27 -15
  16. package/dist/index.d.cts +6 -5
  17. package/dist/index.d.ts +6 -5
  18. package/dist/index.js +1 -1
  19. package/dist/persist.cjs +22 -3
  20. package/dist/persist.cjs.map +1 -1
  21. package/dist/persist.d.cts +3 -1
  22. package/dist/persist.d.ts +3 -1
  23. package/dist/persist.js +22 -3
  24. package/dist/persist.js.map +1 -1
  25. package/dist/popout.cjs +3 -4
  26. package/dist/popout.cjs.map +1 -1
  27. package/dist/popout.d.cts +1 -1
  28. package/dist/popout.d.ts +1 -1
  29. package/dist/popout.js +3 -4
  30. package/dist/popout.js.map +1 -1
  31. package/dist/react.cjs +4 -3
  32. package/dist/react.cjs.map +1 -1
  33. package/dist/react.d.cts +2 -2
  34. package/dist/react.d.ts +2 -2
  35. package/dist/react.js +2 -1
  36. package/dist/react.js.map +1 -1
  37. package/dist/solid.cjs +4 -3
  38. package/dist/solid.cjs.map +1 -1
  39. package/dist/solid.d.cts +2 -2
  40. package/dist/solid.d.ts +2 -2
  41. package/dist/solid.js +2 -1
  42. package/dist/solid.js.map +1 -1
  43. package/dist/svelte.cjs +8 -5
  44. package/dist/svelte.cjs.map +1 -1
  45. package/dist/svelte.d.cts +2 -2
  46. package/dist/svelte.d.ts +2 -2
  47. package/dist/svelte.js +6 -3
  48. package/dist/svelte.js.map +1 -1
  49. package/dist/themes/light.css +202 -0
  50. package/dist/themes/retro.css +154 -0
  51. package/dist/{types-Bkrq2djr.d.cts → types-t6pyVYKN.d.cts} +38 -2
  52. package/dist/{types-Bkrq2djr.d.ts → types-t6pyVYKN.d.ts} +38 -2
  53. package/dist/vue.cjs +4 -3
  54. package/dist/vue.cjs.map +1 -1
  55. package/dist/vue.d.cts +2 -2
  56. package/dist/vue.d.ts +2 -2
  57. package/dist/vue.js +2 -1
  58. package/dist/vue.js.map +1 -1
  59. package/package.json +34 -7
  60. package/dist/chunk-2TVOEDRG.js.map +0 -1
  61. package/dist/chunk-KQCVZ2W7.cjs.map +0 -1
package/README.md CHANGED
@@ -14,13 +14,18 @@
14
14
 
15
15
  - 🪟 **Full window lifecycle** — open, close, focus, minimize, maximize, restore, drag, 8-direction resize
16
16
  - 🧠 **Headless core** — a serializable state machine plus a DOM controller; bring your own markup or use the glass theme
17
- - ⚛️ **Official adapters** — `@surdeddd/wmkit/react`, `@surdeddd/wmkit/vue`, `@surdeddd/wmkit/svelte`, `@surdeddd/wmkit/solid`, all thin sugar over one core
18
- - ⊞ **Snap zones** — halves, quarters and drag-to-top maximize with a live preview
19
- - ⌨️ **Accessible** — keyboard move/resize, F6 window cycling, focus-trapped modals, `aria-live` announcements
17
+ - ⚛️ **Official adapters** — `@surdeddd/wmkit/react`, `/vue`, `/svelte`, `/solid`, `/angular`, all thin sugar over one core
18
+ - ⊞ **Snap zones** — halves, quarters, thirds and drag-to-top maximize with a live preview
19
+ - 🧲 **Magnetism** — window edges align to neighbours and the viewport while dragging
20
+ - ↩️ **Undo/redo** — every mutation is one step; a whole drag collapses into a single history entry
21
+ - 🗂️ **Workspaces & layouts** — virtual desktops, saved snapshots, `cascade`/`tile` in one call
22
+ - 🔒 **Aspect ratio lock** — pin a window to 16:9 or 4:3 and resize stays honest
23
+ - ⌨️ **Accessible** — keyboard move/resize/snap, F6 window cycling, focus-trapped modals, `aria-live` announcements
20
24
  - ⚡ **Fast** — `transform`-only positioning, rAF-batched pointer input, structural sharing; 50 windows drag at 60fps
21
- - 💾 **Persistence** — one call to serialize the desktop, one call to restore it
25
+ - 💾 **Persistence** — one call to serialize the desktop, one call to restore it, with versioned migrations
26
+ - 🎨 **Three themes** — dark glass, light glass and Win98 retro, or bring your own CSS
22
27
  - 🖼️ **Popout** *(experimental)* — send a window into Document Picture-in-Picture
23
- - 📦 **Zero dependencies**, strict TypeScript, ESM + CJS, ~9 kB gzip core
28
+ - 📦 **Zero dependencies**, strict TypeScript, ESM + CJS, ~10.7 kB brotli core
24
29
 
25
30
  ## Install
26
31
 
@@ -190,9 +195,44 @@ function Desktop() {
190
195
  }
191
196
  ```
192
197
 
198
+ ## Angular
199
+
200
+ ```ts
201
+ import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
202
+ import { useWindowManager, createDesktop, useWmState } from '@surdeddd/wmkit/angular'
203
+
204
+ @Component({
205
+ selector: 'app-desktop',
206
+ standalone: true,
207
+ template: `
208
+ <div #desktop style="position: relative; height: 100vh">
209
+ <section #hello>
210
+ <header data-wm-drag><span data-wm-title>Hello</span></header>
211
+ <div data-wm-content>signals inside</div>
212
+ </section>
213
+ </div>
214
+ `,
215
+ })
216
+ export class DesktopComponent implements AfterViewInit {
217
+ wm = useWindowManager()
218
+ dk = createDesktop(this.wm)
219
+ state = useWmState(this.wm)
220
+ @ViewChild('desktop') desktopRef!: ElementRef<HTMLElement>
221
+ @ViewChild('hello') helloRef!: ElementRef<HTMLElement>
222
+
223
+ ngAfterViewInit(): void {
224
+ this.wm.open({ id: 'hello', title: 'Hello' })
225
+ this.dk.desktop(this.desktopRef.nativeElement)
226
+ this.dk.window('hello')(this.helloRef.nativeElement)
227
+ }
228
+ }
229
+ ```
230
+
231
+ `useWmState` returns a read-only `Signal<ManagerState>` and `useWmWindow(wm, id)` a computed per-window signal, so templates track updates fine-grained. Hooks called in an injection context clean up through `DestroyRef` automatically; outside one they simply skip auto-cleanup.
232
+
193
233
  ## Snap zones in action
194
234
 
195
- [![Snap zones — window tiled to the left half](https://raw.githubusercontent.com/Surdeddd/wmkit/main/.github/assets/snap.png)](https://surdeddd.github.io/wmkit/)
235
+ [![Snap zones — three windows tiled into thirds](https://raw.githubusercontent.com/Surdeddd/wmkit/main/.github/assets/snap.png)](https://surdeddd.github.io/wmkit/)
196
236
 
197
237
  Throw a window against an edge or corner — a live preview shows the target zone, releasing tiles it. Halves, quarters, and drag-to-top maximize.
198
238
 
@@ -211,6 +251,8 @@ interface ManagerOptions {
211
251
  cascadeOffset?: number // auto-position step for new windows (default 32)
212
252
  cascadeOrigin?: { x: number; y: number }
213
253
  idPrefix?: string
254
+ historyLimit?: number // undo/redo depth (default 50, 0 disables history)
255
+ workspace?: number // initial virtual desktop (default 0)
214
256
  }
215
257
  ```
216
258
 
@@ -222,12 +264,19 @@ Manager methods:
222
264
  | `close(id)` / `closeAll()` | focus moves to the next eligible window |
223
265
  | `focus(id)` / `blur()` / `cycleFocus(dir?)` | focusing a minimized window restores it; modals block focus below them |
224
266
  | `minimize(id)` / `maximize(id)` / `restore(id)` / `toggleMaximize(id)` | restore returns to the pre-minimize stage, including maximized/snapped |
225
- | `snap(id, zone)` | `'left' \| 'right' \| 'top' \| 'bottom' \| 'top-left' \| …` |
226
- | `move(id, x, y)` / `moveBy(id, dx, dy)` / `resize(id, patch)` | resizing a snapped window unsnaps it |
267
+ | `snap(id, zone)` | halves, quarters and thirds: `'left' \| 'right' \| 'top' \| 'bottom' \| 'top-left' \| \| 'left-third' \| 'center-third' \| 'right-third'` |
268
+ | `move(id, x, y)` / `moveBy(id, dx, dy)` / `resize(id, patch)` | resizing a snapped window unsnaps it; `aspectRatio` is preserved |
269
+ | `center(id)` / `sendToBack(id)` | centre in the viewport, or drop to the bottom of the window's layer |
227
270
  | `restoreTo(id, bounds)` | used for drag-off-snap; stage → `normal` at explicit bounds |
228
- | `update(id, patch)` | title, layer, min/max size, per-window flags, `meta` |
229
- | `setViewport(size)` | re-derives maximized/snapped bounds, clamps the rest |
230
- | `serialize()` / `hydrate(data)` | JSON-safe snapshot of the whole desktop |
271
+ | `update(id, patch)` | title, layer, min/max size, `aspectRatio`, per-window flags, `meta` |
272
+ | `setViewport(size)` | re-derives maximized/snapped bounds, clamps the rest; never recorded in history |
273
+ | `workspace()` / `setWorkspace(n)` / `moveToWorkspace(id, n)` | virtual desktops; focusing a window switches to its workspace |
274
+ | `serialize()` / `hydrate(data)` | JSON-safe snapshot of the whole desktop, re-derived against the current viewport |
275
+ | `undo()` / `redo()` / `canUndo()` / `canRedo()` / `clearHistory()` | every mutation is one step; a whole drag or resize collapses into a single entry |
276
+ | `beginInteraction()` / `endInteraction()` / `abortInteraction()` | group a live gesture into one history entry, or drop it entirely on cancel |
277
+ | `saveLayout(name)` / `loadLayout(name)` / `deleteLayout(name)` / `layoutNames()` | named desktop snapshots; `getLayout`/`setLayout` for external storage |
278
+ | `arrange('cascade' \| 'tile')` | cascade staggers restored sizes, tile fills the viewport in a grid |
279
+ | `minimizeAll()` / `restoreAll()` | bulk stage switches in one history step |
231
280
  | `subscribe(fn)` / `on(event, fn)` | granular events: `open, close, focus, move, resize, stage, update, order, modalblocked` |
232
281
  | `batch(fn)` | coalesce many operations into one `change` notification |
233
282
 
@@ -240,14 +289,17 @@ DOM controller: pointer drag with capture (touch/pen included), 8-direction resi
240
289
  ```ts
241
290
  interface DesktopOptions {
242
291
  snap?: boolean | { threshold?: number; cornerSize?: number; preview?: boolean; topEdge?: 'maximize' | 'top' | 'none' }
243
- keyboard?: boolean | { moveStep?: number; cycle?: boolean }
292
+ keyboard?: boolean | { moveStep?: number; cycle?: boolean; snapShortcuts?: boolean; historyShortcuts?: boolean }
244
293
  announce?: boolean | Partial<AnnouncerMessages> // localize screen-reader strings here
245
294
  autoViewport?: boolean // ResizeObserver → wm.setViewport (default true)
295
+ magnetism?: boolean | { threshold?: number } // edge-align to neighbours + viewport while dragging (default on, 8 px / 12 px coarse)
296
+ hitAreas?: { edge?: number; corner?: number } // resize handle thickness (auto-doubles on touch)
246
297
  minimizeTarget?: (win: WindowState) => Element | null // FLIP ghost target on minimize
298
+ onTitlebarContextMenu?: (win: WindowState, event: MouseEvent) => void // right-click / long-press hook for your own menu
247
299
  }
248
300
  ```
249
301
 
250
- Keyboard defaults: arrows move the focused window (16 px), `Alt` for 1 px steps, `Shift+arrows` resize, `F6` / `Shift+F6` cycle windows, `Escape` cancels an in-flight drag or resize.
302
+ Keyboard defaults: arrows move the focused window (16 px), `Alt` for 1 px steps, `Shift+arrows` resize, `Ctrl/⌘+Alt+←/→` snap to a half, `Ctrl/⌘+Alt+↑/↓` maximize or minimize, `Ctrl/⌘+Z` / `Ctrl/⌘+Shift+Z` undo and redo, `F6` / `Shift+F6` cycle windows, `Escape` cancels an in-flight drag or resize (and drops it from the history).
251
303
 
252
304
  ### `persist(wm, options?)` — `@surdeddd/wmkit/persist`
253
305
 
@@ -260,6 +312,15 @@ store.clear()
260
312
 
261
313
  Storage defaults to `localStorage` (probed safely — SSR and private-mode friendly) and accepts any `getItem/setItem/removeItem` implementation.
262
314
 
315
+ Payloads are wrapped in a `{ version, state }` envelope. Bump `version` when your window contract changes and pass `migrate` to upgrade older data — anything you cannot migrate is discarded instead of restored broken:
316
+
317
+ ```js
318
+ persist(wm, {
319
+ version: 2,
320
+ migrate: (state, from) => (from === 1 ? upgrade(state) : null),
321
+ })
322
+ ```
323
+
263
324
  ### `popout(wm, id, contentEl, options?)` — `@surdeddd/wmkit/popout` *(experimental)*
264
325
 
265
326
  Moves a window's content into a [Document Picture-in-Picture](https://developer.mozilla.org/docs/Web/API/Document_Picture-in-Picture_API) always-on-top OS window, keeping the same JS context and state. Feature-detect with `isPopoutSupported()`.
@@ -277,6 +338,8 @@ Moves a window's content into a [Document Picture-in-Picture](https://developer.
277
338
  }
278
339
  ```
279
340
 
341
+ Two more ready-made themes ship alongside: `themes/light.css` (light glass) and `themes/retro.css` (pixel-perfect Win98 nostalgia). All three style the same `data-wm-*` attributes, so switching is a one-line import swap.
342
+
280
343
  Skip the import entirely and the library stays headless: state attributes (`data-wm-stage`, `data-wm-focused`, `data-wm-dragging`, `data-wm-flash`, `[hidden]`) are yours to style.
281
344
 
282
345
  ## SSR
@@ -289,7 +352,7 @@ The core never touches `window`/`document` — create managers and even `hydrate
289
352
  | --- | --- | --- | --- | --- | --- |
290
353
  | Maintained | ✓ 2026 | ✗ since 2023 | ✗ since 2022 | ✓ | ✓ |
291
354
  | Headless core | ✓ | ✗ | ✗ | ~ own UI | ✓ |
292
- | Official adapters | React·Vue·Svelte·Solid | community | ✗ | React·Vue·Angular | via Ark UI |
355
+ | Official adapters | React·Vue·Svelte·Solid·Angular | community | ✗ | React·Vue·Angular | via Ark UI |
293
356
  | Multi-window (z-order, taskbar, modals) | ✓ | partial | partial | dock groups | ✗ single panel |
294
357
  | Snap zones + preview | ✓ | ✗ | ✗ | — | ✗ |
295
358
  | Keyboard + screen reader | ✓ | ✗ | ✗ | partial | partial |
@@ -301,8 +364,9 @@ The core never touches `window`/`document` — create managers and even `hydrate
301
364
 
302
365
  ## Quality
303
366
 
304
- - 121 unit tests, **100%** line/branch/function/statement coverage on the core state machine and persistence
305
- - 160+ Playwright scenarios on Chromium, WebKit and mobile emulation: drag, 8-way resize, snap, keyboard, touch, persistence across reloads, 50-window stress, modal traps, axe accessibility scans
367
+ - 216 unit tests, **100%** line/branch/function/statement coverage on the core state machine and persistence
368
+ - 190+ Playwright scenarios on Chromium, WebKit and mobile emulation: drag, 8-way resize, snap, magnetism, workspaces, undo after drag, keyboard, touch, persistence across reloads, 50-window stress, modal traps, axe accessibility scans, visual regression screenshots
369
+ - performance benchmarks run in CI on every push (`vitest bench`): 1 000 windows open in ~150 ms, a move among 50 windows costs ~1.2 µs, a full 100-step undo/redo sweep ~52 µs
306
370
  - `publint` + `@arethetypeswrong/cli` validate the published package, `size-limit` guards bundle budgets
307
371
 
308
372
  ## Development
package/README.ru.md CHANGED
@@ -10,13 +10,18 @@
10
10
 
11
11
  - 🪟 **Полный жизненный цикл окна** — открытие, закрытие, фокус, сворачивание, разворачивание, восстановление, drag, ресайз в 8 направлениях
12
12
  - 🧠 **Headless-ядро** — сериализуемая стейт-машина плюс DOM-контроллер; своя разметка или готовая стеклянная тема
13
- - ⚛️ **Родные адаптеры** — `@surdeddd/wmkit/react`, `@surdeddd/wmkit/vue`, `@surdeddd/wmkit/svelte`, `@surdeddd/wmkit/solid`, тонкий сахар над одним ядром
14
- - ⊞ **Snap-зоны** — половины, четверти и максимизация от верхнего края с живым превью
15
- - ⌨️ **Доступность**move/resize с клавиатуры, цикл окон по F6, focus-trap в модалках, `aria-live`-анонсы
13
+ - ⚛️ **Родные адаптеры** — `@surdeddd/wmkit/react`, `/vue`, `/svelte`, `/solid`, `/angular`, тонкий сахар над одним ядром
14
+ - ⊞ **Snap-зоны** — половины, четверти, трети и максимизация от верхнего края с живым превью
15
+ - 🧲 **Магнетизм**края окна прилипают к соседям и вьюпорту при перетаскивании
16
+ - ↩️ **Undo/redo** — каждая мутация = один шаг; целый drag схлопывается в одну запись истории
17
+ - 🗂️ **Рабочие столы и layout'ы** — виртуальные столы, снапшоты, `cascade`/`tile` одним вызовом
18
+ - 🔒 **Фиксация пропорций** — окно с `aspectRatio` держит 16:9 или 4:3 при любом ресайзе
19
+ - ⌨️ **Доступность** — move/resize/snap с клавиатуры, цикл окон по F6, focus-trap в модалках, `aria-live`-анонсы
16
20
  - ⚡ **Производительность** — позиционирование только через `transform`, rAF-батчинг ввода, structural sharing; 50 окон таскаются на 60fps
17
- - 💾 **Персист** — один вызов сериализует рабочий стол, один — восстанавливает
21
+ - 💾 **Персист** — один вызов сериализует рабочий стол, один — восстанавливает, с версионированием и миграциями
22
+ - 🎨 **Три темы** — тёмное стекло, светлое стекло и Win98-ретро, либо полностью свой CSS
18
23
  - 🖼️ **Popout** *(experimental)* — вынос окна в Document Picture-in-Picture
19
- - 📦 **Ноль зависимостей**, строгий TypeScript, ESM + CJS, ~9 kB gzip
24
+ - 📦 **Ноль зависимостей**, строгий TypeScript, ESM + CJS, ~10.7 kB brotli
20
25
 
21
26
  ## Установка
22
27
 
@@ -68,7 +73,7 @@ desktop.attachWindow(win.id, el, { removeOnClose: true })
68
73
 
69
74
  ## Адаптеры
70
75
 
71
- Примеры для React, Vue, Svelte и Solid — в [английском README](./README.md#react) и на [лендинге](https://surdeddd.github.io/wmkit/) (табы «Фреймворки»). Принцип один: контент окна живёт в дереве вашего фреймворка, никакого innerHTML.
76
+ Примеры для React, Vue, Svelte, Solid и Angular — в [английском README](./README.md#react) и на [лендинге](https://surdeddd.github.io/wmkit/) (табы «Фреймворки»). Принцип один: контент окна живёт в дереве вашего фреймворка, никакого innerHTML.
72
77
 
73
78
  ## API ядра — кратко
74
79
 
@@ -85,17 +90,33 @@ wm.hydrate(json)
85
90
 
86
91
  wm.on('stage', ({ window, previous }) => console.log(previous, '→', window.stage))
87
92
  wm.batch(() => { /* много операций — одно уведомление */ })
93
+
94
+ wm.undo(); wm.redo() // история изменений, drag = одна запись (historyLimit, default 50)
95
+ wm.saveLayout('работа'); wm.loadLayout('работа') // именованные снапшоты рабочего стола
96
+ wm.arrange('tile') // или 'cascade'; плюс minimizeAll() / restoreAll()
97
+
98
+ wm.setWorkspace(1) // виртуальные столы: окна других столов скрыты и не фокусируются
99
+ wm.moveToWorkspace('docs', 1) // focus('docs') сам переключит стол обратно
100
+ wm.open({ id: 'video', aspectRatio: 16 / 9 }) // ресайз держит пропорцию
101
+ wm.center('video'); wm.sendToBack('docs')
88
102
  ```
89
103
 
90
104
  Слои: `normal` < `floating` (always-on-top) < `modal`. Модалка блокирует фокус нижних окон (попытка — событие `modalblocked` и flash-анимация), Tab заперт внутри.
91
105
 
92
- Клавиатура по умолчанию: стрелки двигают сфокусированное окно (16 px), `Alt` — шаг 1 px, `Shift+стрелки` — ресайз, `F6`/`Shift+F6` — цикл по окнам, `Escape` отменяет активный drag/resize.
106
+ Клавиатура по умолчанию: стрелки двигают сфокусированное окно (16 px), `Alt` — шаг 1 px, `Shift+стрелки` — ресайз, `Ctrl/⌘+Alt+←/→` — прилипание к половине, `Ctrl/⌘+Alt+↑/↓` — развернуть/свернуть, `Ctrl/⌘+Z` и `Ctrl/⌘+Shift+Z` — отмена и повтор, `F6`/`Shift+F6` — цикл по окнам, `Escape` отменяет активный drag/resize (и убирает его из истории).
107
+
108
+ Магнетизм включён из коробки (порог 8 px, на тач-устройствах 12 px): `attachDesktop(wm, el, { magnetism: { threshold: 16 } })` или `magnetism: false`. Свой контекст-меню тайтлбара — через `onTitlebarContextMenu(win, event)` (правый клик и long-press на таче).
93
109
 
94
110
  ### Персист
95
111
 
96
112
  ```js
97
113
  import { persist } from '@surdeddd/wmkit/persist'
98
114
  persist(wm, { key: 'my-desktop' }) // авто-восстановление + debounce-сохранение
115
+
116
+ persist(wm, { // версия контракта + миграция старых данных
117
+ version: 2,
118
+ migrate: (state, from) => (from === 1 ? upgrade(state) : null),
119
+ })
99
120
  ```
100
121
 
101
122
  ### Popout (experimental)
@@ -111,14 +132,17 @@ if (isPopoutSupported()) await popout(wm, 'docs', contentElement)
111
132
 
112
133
  Подключите `@surdeddd/wmkit/themes/glass.css` и переопределяйте CSS-переменные (`--wm-radius`, `--wm-bg`, `--wm-accent`, …) — или не подключайте ничего и стилизуйте `data-wm-stage`, `data-wm-focused`, `data-wm-dragging`, `[hidden]` сами.
113
134
 
135
+ Ещё две готовые темы: `themes/light.css` (светлое стекло) и `themes/retro.css` (Win98-ностальгия). Все три стилизуют одни и те же `data-wm-*` атрибуты — переключение = замена одного импорта.
136
+
114
137
  ## SSR
115
138
 
116
139
  Ядро не трогает `window`/`document`: менеджер можно создавать и гидрейтить на сервере, `attachDesktop` вызывается после маунта. `persist` тихо выключается без доступного storage.
117
140
 
118
141
  ## Качество
119
142
 
120
- - 121 юнит-тест, **100%** покрытие стейт-машины и persist по строкам/веткам/функциям
121
- - 160+ Playwright-сценариев на Chromium, WebKit и мобильной эмуляции: drag, ресайз во все стороны, снэп, клавиатура, touch, персист через перезагрузку, стресс на 50 окон, модальные ловушки, axe-аудиты доступности
143
+ - 216 юнит-тестов, **100%** покрытие стейт-машины и persist по строкам/веткам/функциям
144
+ - 190+ Playwright-сценариев на Chromium, WebKit и мобильной эмуляции: drag, ресайз во все стороны, снэп, магнетизм, рабочие столы, undo после drag, клавиатура, touch, персист через перезагрузку, стресс на 50 окон, модальные ловушки, axe-аудиты доступности, визуальная регрессия по скриншотам
145
+ - перф-бенчмарки в CI на каждый push (`vitest bench`): 1 000 окон открываются за ~150 мс, move среди 50 окон ~1.2 мкс, полный undo/redo-проход на 100 шагов ~52 мкс
122
146
  - `publint` + `@arethetypeswrong/cli` проверяют валидность пакета, `size-limit` следит за бюджетами
123
147
 
124
148
  ## Лицензия
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ var chunk3KZZOLHW_cjs = require('./chunk-3KZZOLHW.cjs');
4
+ var core = require('@angular/core');
5
+
6
+ function onDestroy(dispose) {
7
+ let destroyRef = null;
8
+ try {
9
+ destroyRef = core.inject(core.DestroyRef);
10
+ } catch {
11
+ destroyRef = null;
12
+ }
13
+ destroyRef?.onDestroy(dispose);
14
+ }
15
+ function useWindowManager(options) {
16
+ const wm = chunk3KZZOLHW_cjs.createWindowManager(options);
17
+ onDestroy(() => wm.destroy());
18
+ return wm;
19
+ }
20
+ function useWmState(wm) {
21
+ const state = core.signal(wm.getState());
22
+ const stop = wm.subscribe((next) => state.set(next));
23
+ onDestroy(stop);
24
+ return state.asReadonly();
25
+ }
26
+ function useWmWindow(wm, id) {
27
+ const state = useWmState(wm);
28
+ return core.computed(() => state().windows[id]);
29
+ }
30
+ function createDesktop(wm, options) {
31
+ const binder = chunk3KZZOLHW_cjs.createDesktopBinder(wm, options);
32
+ onDestroy(() => binder.destroy());
33
+ return {
34
+ binder,
35
+ desktop(element) {
36
+ const unbind = binder.bindDesktop(element);
37
+ onDestroy(unbind);
38
+ },
39
+ window(id, windowOptions) {
40
+ return (element) => {
41
+ const unbind = binder.bindWindow(id, element, windowOptions);
42
+ onDestroy(unbind);
43
+ };
44
+ }
45
+ };
46
+ }
47
+
48
+ exports.createDesktop = createDesktop;
49
+ exports.useWindowManager = useWindowManager;
50
+ exports.useWmState = useWmState;
51
+ exports.useWmWindow = useWmWindow;
52
+ //# sourceMappingURL=angular.cjs.map
53
+ //# sourceMappingURL=angular.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/angular.ts"],"names":["inject","DestroyRef","createWindowManager","signal","computed","createDesktopBinder"],"mappings":";;;;;AAMA,SAAS,UAAU,OAAA,EAA2B;AAC5C,EAAA,IAAI,UAAA,GAAgC,IAAA;AACpC,EAAA,IAAI;AACF,IAAA,UAAA,GAAaA,YAAOC,eAAU,CAAA;AAAA,EAChC,CAAA,CAAA,MAAQ;AACN,IAAA,UAAA,GAAa,IAAA;AAAA,EACf;AACA,EAAA,UAAA,EAAY,UAAU,OAAO,CAAA;AAC/B;AAEO,SAAS,iBAAiB,OAAA,EAAyC;AACxE,EAAA,MAAM,EAAA,GAAKC,sCAAoB,OAAO,CAAA;AACtC,EAAA,SAAA,CAAU,MAAM,EAAA,CAAG,OAAA,EAAS,CAAA;AAC5B,EAAA,OAAO,EAAA;AACT;AAEO,SAAS,WAAW,EAAA,EAAyC;AAClE,EAAA,MAAM,KAAA,GAAQC,WAAA,CAAO,EAAA,CAAG,QAAA,EAAU,CAAA;AAClC,EAAA,MAAM,IAAA,GAAO,GAAG,SAAA,CAAU,CAAC,SAAS,KAAA,CAAM,GAAA,CAAI,IAAI,CAAC,CAAA;AACnD,EAAA,SAAA,CAAU,IAAI,CAAA;AACd,EAAA,OAAO,MAAM,UAAA,EAAW;AAC1B;AAEO,SAAS,WAAA,CAAY,IAAmB,EAAA,EAA6C;AAC1F,EAAA,MAAM,KAAA,GAAQ,WAAW,EAAE,CAAA;AAC3B,EAAA,OAAOC,cAAS,MAAM,KAAA,EAAM,CAAE,OAAA,CAAQ,EAAE,CAAC,CAAA;AAC3C;AAQO,SAAS,aAAA,CAAc,IAAmB,OAAA,EAA0C;AACzF,EAAA,MAAM,MAAA,GAASC,qCAAA,CAAoB,EAAA,EAAI,OAAO,CAAA;AAC9C,EAAA,SAAA,CAAU,MAAM,MAAA,CAAO,OAAA,EAAS,CAAA;AAChC,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,QAAQ,OAAA,EAAS;AACf,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,WAAA,CAAY,OAAO,CAAA;AACzC,MAAA,SAAA,CAAU,MAAM,CAAA;AAAA,IAClB,CAAA;AAAA,IACA,MAAA,CAAO,IAAI,aAAA,EAAe;AACxB,MAAA,OAAO,CAAC,OAAA,KAAY;AAClB,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,UAAA,CAAW,EAAA,EAAI,SAAS,aAAa,CAAA;AAC3D,QAAA,SAAA,CAAU,MAAM,CAAA;AAAA,MAClB,CAAA;AAAA,IACF;AAAA,GACF;AACF","file":"angular.cjs","sourcesContent":["import { computed, DestroyRef, inject, type Signal, signal } from '@angular/core'\nimport { createWindowManager } from '../core/manager'\nimport type { ManagerOptions, ManagerState, WindowManager, WindowState } from '../core/types'\nimport { createDesktopBinder, type DesktopBinder } from '../dom/binder'\nimport type { DesktopOptions, WindowAttachOptions } from '../dom/shared'\n\nfunction onDestroy(dispose: () => void): void {\n let destroyRef: DestroyRef | null = null\n try {\n destroyRef = inject(DestroyRef)\n } catch {\n destroyRef = null\n }\n destroyRef?.onDestroy(dispose)\n}\n\nexport function useWindowManager(options?: ManagerOptions): WindowManager {\n const wm = createWindowManager(options)\n onDestroy(() => wm.destroy())\n return wm\n}\n\nexport function useWmState(wm: WindowManager): Signal<ManagerState> {\n const state = signal(wm.getState())\n const stop = wm.subscribe((next) => state.set(next))\n onDestroy(stop)\n return state.asReadonly()\n}\n\nexport function useWmWindow(wm: WindowManager, id: string): Signal<WindowState | undefined> {\n const state = useWmState(wm)\n return computed(() => state().windows[id])\n}\n\nexport interface AngularDesktop {\n binder: DesktopBinder\n desktop(element: HTMLElement): void\n window(id: string, options?: WindowAttachOptions): (element: HTMLElement) => void\n}\n\nexport function createDesktop(wm: WindowManager, options?: DesktopOptions): AngularDesktop {\n const binder = createDesktopBinder(wm, options)\n onDestroy(() => binder.destroy())\n return {\n binder,\n desktop(element) {\n const unbind = binder.bindDesktop(element)\n onDestroy(unbind)\n },\n window(id, windowOptions) {\n return (element) => {\n const unbind = binder.bindWindow(id, element, windowOptions)\n onDestroy(unbind)\n }\n },\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { Signal } from '@angular/core';
2
+ import { W as WindowManager, M as ManagerOptions, a as ManagerState, b as WindowState } from './types-t6pyVYKN.cjs';
3
+ import { D as DesktopBinder, W as WindowAttachOptions, a as DesktopOptions } from './binder-Dl07SXIM.cjs';
4
+
5
+ declare function useWindowManager(options?: ManagerOptions): WindowManager;
6
+ declare function useWmState(wm: WindowManager): Signal<ManagerState>;
7
+ declare function useWmWindow(wm: WindowManager, id: string): Signal<WindowState | undefined>;
8
+ interface AngularDesktop {
9
+ binder: DesktopBinder;
10
+ desktop(element: HTMLElement): void;
11
+ window(id: string, options?: WindowAttachOptions): (element: HTMLElement) => void;
12
+ }
13
+ declare function createDesktop(wm: WindowManager, options?: DesktopOptions): AngularDesktop;
14
+
15
+ export { type AngularDesktop, createDesktop, useWindowManager, useWmState, useWmWindow };
@@ -0,0 +1,15 @@
1
+ import { Signal } from '@angular/core';
2
+ import { W as WindowManager, M as ManagerOptions, a as ManagerState, b as WindowState } from './types-t6pyVYKN.js';
3
+ import { D as DesktopBinder, W as WindowAttachOptions, a as DesktopOptions } from './binder-B2A2IZGh.js';
4
+
5
+ declare function useWindowManager(options?: ManagerOptions): WindowManager;
6
+ declare function useWmState(wm: WindowManager): Signal<ManagerState>;
7
+ declare function useWmWindow(wm: WindowManager, id: string): Signal<WindowState | undefined>;
8
+ interface AngularDesktop {
9
+ binder: DesktopBinder;
10
+ desktop(element: HTMLElement): void;
11
+ window(id: string, options?: WindowAttachOptions): (element: HTMLElement) => void;
12
+ }
13
+ declare function createDesktop(wm: WindowManager, options?: DesktopOptions): AngularDesktop;
14
+
15
+ export { type AngularDesktop, createDesktop, useWindowManager, useWmState, useWmWindow };
@@ -0,0 +1,48 @@
1
+ import { createWindowManager, createDesktopBinder } from './chunk-FPZDAKNP.js';
2
+ import { signal, computed, inject, DestroyRef } from '@angular/core';
3
+
4
+ function onDestroy(dispose) {
5
+ let destroyRef = null;
6
+ try {
7
+ destroyRef = inject(DestroyRef);
8
+ } catch {
9
+ destroyRef = null;
10
+ }
11
+ destroyRef?.onDestroy(dispose);
12
+ }
13
+ function useWindowManager(options) {
14
+ const wm = createWindowManager(options);
15
+ onDestroy(() => wm.destroy());
16
+ return wm;
17
+ }
18
+ function useWmState(wm) {
19
+ const state = signal(wm.getState());
20
+ const stop = wm.subscribe((next) => state.set(next));
21
+ onDestroy(stop);
22
+ return state.asReadonly();
23
+ }
24
+ function useWmWindow(wm, id) {
25
+ const state = useWmState(wm);
26
+ return computed(() => state().windows[id]);
27
+ }
28
+ function createDesktop(wm, options) {
29
+ const binder = createDesktopBinder(wm, options);
30
+ onDestroy(() => binder.destroy());
31
+ return {
32
+ binder,
33
+ desktop(element) {
34
+ const unbind = binder.bindDesktop(element);
35
+ onDestroy(unbind);
36
+ },
37
+ window(id, windowOptions) {
38
+ return (element) => {
39
+ const unbind = binder.bindWindow(id, element, windowOptions);
40
+ onDestroy(unbind);
41
+ };
42
+ }
43
+ };
44
+ }
45
+
46
+ export { createDesktop, useWindowManager, useWmState, useWmWindow };
47
+ //# sourceMappingURL=angular.js.map
48
+ //# sourceMappingURL=angular.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/angular.ts"],"names":[],"mappings":";;;AAMA,SAAS,UAAU,OAAA,EAA2B;AAC5C,EAAA,IAAI,UAAA,GAAgC,IAAA;AACpC,EAAA,IAAI;AACF,IAAA,UAAA,GAAa,OAAO,UAAU,CAAA;AAAA,EAChC,CAAA,CAAA,MAAQ;AACN,IAAA,UAAA,GAAa,IAAA;AAAA,EACf;AACA,EAAA,UAAA,EAAY,UAAU,OAAO,CAAA;AAC/B;AAEO,SAAS,iBAAiB,OAAA,EAAyC;AACxE,EAAA,MAAM,EAAA,GAAK,oBAAoB,OAAO,CAAA;AACtC,EAAA,SAAA,CAAU,MAAM,EAAA,CAAG,OAAA,EAAS,CAAA;AAC5B,EAAA,OAAO,EAAA;AACT;AAEO,SAAS,WAAW,EAAA,EAAyC;AAClE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,EAAA,CAAG,QAAA,EAAU,CAAA;AAClC,EAAA,MAAM,IAAA,GAAO,GAAG,SAAA,CAAU,CAAC,SAAS,KAAA,CAAM,GAAA,CAAI,IAAI,CAAC,CAAA;AACnD,EAAA,SAAA,CAAU,IAAI,CAAA;AACd,EAAA,OAAO,MAAM,UAAA,EAAW;AAC1B;AAEO,SAAS,WAAA,CAAY,IAAmB,EAAA,EAA6C;AAC1F,EAAA,MAAM,KAAA,GAAQ,WAAW,EAAE,CAAA;AAC3B,EAAA,OAAO,SAAS,MAAM,KAAA,EAAM,CAAE,OAAA,CAAQ,EAAE,CAAC,CAAA;AAC3C;AAQO,SAAS,aAAA,CAAc,IAAmB,OAAA,EAA0C;AACzF,EAAA,MAAM,MAAA,GAAS,mBAAA,CAAoB,EAAA,EAAI,OAAO,CAAA;AAC9C,EAAA,SAAA,CAAU,MAAM,MAAA,CAAO,OAAA,EAAS,CAAA;AAChC,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,QAAQ,OAAA,EAAS;AACf,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,WAAA,CAAY,OAAO,CAAA;AACzC,MAAA,SAAA,CAAU,MAAM,CAAA;AAAA,IAClB,CAAA;AAAA,IACA,MAAA,CAAO,IAAI,aAAA,EAAe;AACxB,MAAA,OAAO,CAAC,OAAA,KAAY;AAClB,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,UAAA,CAAW,EAAA,EAAI,SAAS,aAAa,CAAA;AAC3D,QAAA,SAAA,CAAU,MAAM,CAAA;AAAA,MAClB,CAAA;AAAA,IACF;AAAA,GACF;AACF","file":"angular.js","sourcesContent":["import { computed, DestroyRef, inject, type Signal, signal } from '@angular/core'\nimport { createWindowManager } from '../core/manager'\nimport type { ManagerOptions, ManagerState, WindowManager, WindowState } from '../core/types'\nimport { createDesktopBinder, type DesktopBinder } from '../dom/binder'\nimport type { DesktopOptions, WindowAttachOptions } from '../dom/shared'\n\nfunction onDestroy(dispose: () => void): void {\n let destroyRef: DestroyRef | null = null\n try {\n destroyRef = inject(DestroyRef)\n } catch {\n destroyRef = null\n }\n destroyRef?.onDestroy(dispose)\n}\n\nexport function useWindowManager(options?: ManagerOptions): WindowManager {\n const wm = createWindowManager(options)\n onDestroy(() => wm.destroy())\n return wm\n}\n\nexport function useWmState(wm: WindowManager): Signal<ManagerState> {\n const state = signal(wm.getState())\n const stop = wm.subscribe((next) => state.set(next))\n onDestroy(stop)\n return state.asReadonly()\n}\n\nexport function useWmWindow(wm: WindowManager, id: string): Signal<WindowState | undefined> {\n const state = useWmState(wm)\n return computed(() => state().windows[id])\n}\n\nexport interface AngularDesktop {\n binder: DesktopBinder\n desktop(element: HTMLElement): void\n window(id: string, options?: WindowAttachOptions): (element: HTMLElement) => void\n}\n\nexport function createDesktop(wm: WindowManager, options?: DesktopOptions): AngularDesktop {\n const binder = createDesktopBinder(wm, options)\n onDestroy(() => binder.destroy())\n return {\n binder,\n desktop(element) {\n const unbind = binder.bindDesktop(element)\n onDestroy(unbind)\n },\n window(id, windowOptions) {\n return (element) => {\n const unbind = binder.bindWindow(id, element, windowOptions)\n onDestroy(unbind)\n }\n },\n }\n}\n"]}
@@ -1,15 +1,23 @@
1
- import { B as Bounds, f as Size, g as SnapZone, W as WindowManager, b as WindowState } from './types-Bkrq2djr.cjs';
1
+ import { f as Size, B as Bounds, g as SnapZone, W as WindowManager, b as WindowState } from './types-t6pyVYKN.js';
2
2
 
3
3
  declare function clamp(value: number, min: number, max: number): number;
4
4
  declare function clampSize(size: Size, min: Size, max: Size | null): Size;
5
5
  declare function clampToViewport(bounds: Bounds, viewport: Size, minVisible: number): Bounds;
6
6
  declare function boundsEqual(a: Bounds, b: Bounds): boolean;
7
+ declare function applyAspect(size: Size, ratio: number, min: Size, max: Size | null, drive: 'width' | 'height'): Size;
7
8
  declare function zoneBounds(zone: SnapZone, viewport: Size): Bounds;
8
9
  interface SnapDetectOptions {
9
10
  threshold?: number;
10
11
  cornerSize?: number;
11
12
  }
12
13
  declare function detectSnapZone(x: number, y: number, viewport: Size, options?: SnapDetectOptions): SnapZone | null;
14
+ interface MagnetResult {
15
+ x: number;
16
+ y: number;
17
+ snappedX: boolean;
18
+ snappedY: boolean;
19
+ }
20
+ declare function magnetize(bounds: Bounds, targets: readonly Bounds[], threshold: number): MagnetResult;
13
21
 
14
22
  interface AnnouncerMessages {
15
23
  opened(title: string): string;
@@ -19,6 +27,7 @@ interface AnnouncerMessages {
19
27
  maximized(title: string): string;
20
28
  snapped(title: string, zone: string): string;
21
29
  focused(title: string): string;
30
+ workspace(index: number): string;
22
31
  }
23
32
  declare const defaultMessages: AnnouncerMessages;
24
33
  interface Announcer {
@@ -35,18 +44,25 @@ interface DesktopSnapOptions extends SnapDetectOptions {
35
44
  interface DesktopKeyboardOptions {
36
45
  moveStep?: number;
37
46
  cycle?: boolean;
47
+ snapShortcuts?: boolean;
48
+ historyShortcuts?: boolean;
38
49
  }
39
50
  interface HitAreaOptions {
40
51
  edge?: number;
41
52
  corner?: number;
42
53
  }
54
+ interface MagnetismOptions {
55
+ threshold?: number;
56
+ }
43
57
  interface DesktopOptions {
44
58
  snap?: boolean | DesktopSnapOptions;
45
59
  keyboard?: boolean | DesktopKeyboardOptions;
46
60
  announce?: boolean | Partial<AnnouncerMessages>;
47
61
  autoViewport?: boolean;
48
62
  hitAreas?: HitAreaOptions;
63
+ magnetism?: boolean | MagnetismOptions;
49
64
  minimizeTarget?: (window: WindowState) => Element | null;
65
+ onTitlebarContextMenu?: (window: WindowState, event: MouseEvent) => void;
50
66
  }
51
67
  interface WindowAttachOptions {
52
68
  handle?: HTMLElement | string;
@@ -65,7 +81,8 @@ interface DesktopBinder {
65
81
  controller(): DesktopController | null;
66
82
  bindDesktop(element: HTMLElement): () => void;
67
83
  bindWindow(id: string, element: HTMLElement, options?: WindowAttachOptions): () => void;
84
+ destroy(): void;
68
85
  }
69
86
  declare function createDesktopBinder(wm: WindowManager, options?: DesktopOptions): DesktopBinder;
70
87
 
71
- export { type Announcer as A, type DesktopBinder as D, type HitAreaOptions as H, type SnapDetectOptions as S, type WindowAttachOptions as W, type DesktopController as a, type DesktopOptions as b, type AnnouncerMessages as c, type DesktopKeyboardOptions as d, type DesktopSnapOptions as e, boundsEqual as f, clamp as g, clampSize as h, clampToViewport as i, createAnnouncer as j, createDesktopBinder as k, defaultMessages as l, detectSnapZone as m, zoneBounds as z };
88
+ export { type Announcer as A, type DesktopBinder as D, type HitAreaOptions as H, type MagnetResult as M, type SnapDetectOptions as S, type WindowAttachOptions as W, type DesktopOptions as a, type DesktopController as b, type AnnouncerMessages as c, type DesktopKeyboardOptions as d, type DesktopSnapOptions as e, type MagnetismOptions as f, applyAspect as g, boundsEqual as h, clamp as i, clampSize as j, clampToViewport as k, createAnnouncer as l, createDesktopBinder as m, defaultMessages as n, detectSnapZone as o, magnetize as p, zoneBounds as z };
@@ -1,15 +1,23 @@
1
- import { B as Bounds, f as Size, g as SnapZone, W as WindowManager, b as WindowState } from './types-Bkrq2djr.js';
1
+ import { f as Size, B as Bounds, g as SnapZone, W as WindowManager, b as WindowState } from './types-t6pyVYKN.cjs';
2
2
 
3
3
  declare function clamp(value: number, min: number, max: number): number;
4
4
  declare function clampSize(size: Size, min: Size, max: Size | null): Size;
5
5
  declare function clampToViewport(bounds: Bounds, viewport: Size, minVisible: number): Bounds;
6
6
  declare function boundsEqual(a: Bounds, b: Bounds): boolean;
7
+ declare function applyAspect(size: Size, ratio: number, min: Size, max: Size | null, drive: 'width' | 'height'): Size;
7
8
  declare function zoneBounds(zone: SnapZone, viewport: Size): Bounds;
8
9
  interface SnapDetectOptions {
9
10
  threshold?: number;
10
11
  cornerSize?: number;
11
12
  }
12
13
  declare function detectSnapZone(x: number, y: number, viewport: Size, options?: SnapDetectOptions): SnapZone | null;
14
+ interface MagnetResult {
15
+ x: number;
16
+ y: number;
17
+ snappedX: boolean;
18
+ snappedY: boolean;
19
+ }
20
+ declare function magnetize(bounds: Bounds, targets: readonly Bounds[], threshold: number): MagnetResult;
13
21
 
14
22
  interface AnnouncerMessages {
15
23
  opened(title: string): string;
@@ -19,6 +27,7 @@ interface AnnouncerMessages {
19
27
  maximized(title: string): string;
20
28
  snapped(title: string, zone: string): string;
21
29
  focused(title: string): string;
30
+ workspace(index: number): string;
22
31
  }
23
32
  declare const defaultMessages: AnnouncerMessages;
24
33
  interface Announcer {
@@ -35,18 +44,25 @@ interface DesktopSnapOptions extends SnapDetectOptions {
35
44
  interface DesktopKeyboardOptions {
36
45
  moveStep?: number;
37
46
  cycle?: boolean;
47
+ snapShortcuts?: boolean;
48
+ historyShortcuts?: boolean;
38
49
  }
39
50
  interface HitAreaOptions {
40
51
  edge?: number;
41
52
  corner?: number;
42
53
  }
54
+ interface MagnetismOptions {
55
+ threshold?: number;
56
+ }
43
57
  interface DesktopOptions {
44
58
  snap?: boolean | DesktopSnapOptions;
45
59
  keyboard?: boolean | DesktopKeyboardOptions;
46
60
  announce?: boolean | Partial<AnnouncerMessages>;
47
61
  autoViewport?: boolean;
48
62
  hitAreas?: HitAreaOptions;
63
+ magnetism?: boolean | MagnetismOptions;
49
64
  minimizeTarget?: (window: WindowState) => Element | null;
65
+ onTitlebarContextMenu?: (window: WindowState, event: MouseEvent) => void;
50
66
  }
51
67
  interface WindowAttachOptions {
52
68
  handle?: HTMLElement | string;
@@ -65,7 +81,8 @@ interface DesktopBinder {
65
81
  controller(): DesktopController | null;
66
82
  bindDesktop(element: HTMLElement): () => void;
67
83
  bindWindow(id: string, element: HTMLElement, options?: WindowAttachOptions): () => void;
84
+ destroy(): void;
68
85
  }
69
86
  declare function createDesktopBinder(wm: WindowManager, options?: DesktopOptions): DesktopBinder;
70
87
 
71
- export { type Announcer as A, type DesktopBinder as D, type HitAreaOptions as H, type SnapDetectOptions as S, type WindowAttachOptions as W, type DesktopController as a, type DesktopOptions as b, type AnnouncerMessages as c, type DesktopKeyboardOptions as d, type DesktopSnapOptions as e, boundsEqual as f, clamp as g, clampSize as h, clampToViewport as i, createAnnouncer as j, createDesktopBinder as k, defaultMessages as l, detectSnapZone as m, zoneBounds as z };
88
+ export { type Announcer as A, type DesktopBinder as D, type HitAreaOptions as H, type MagnetResult as M, type SnapDetectOptions as S, type WindowAttachOptions as W, type DesktopOptions as a, type DesktopController as b, type AnnouncerMessages as c, type DesktopKeyboardOptions as d, type DesktopSnapOptions as e, type MagnetismOptions as f, applyAspect as g, boundsEqual as h, clamp as i, clampSize as j, clampToViewport as k, createAnnouncer as l, createDesktopBinder as m, defaultMessages as n, detectSnapZone as o, magnetize as p, zoneBounds as z };