@surdeddd/wmkit 0.2.0 → 0.3.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 (45) hide show
  1. package/README.md +55 -5
  2. package/README.ru.md +18 -5
  3. package/dist/angular.cjs +52 -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 +47 -0
  8. package/dist/angular.js.map +1 -0
  9. package/dist/{binder-BY-saDUB.d.cts → binder-0Oku22Nc.d.ts} +14 -2
  10. package/dist/{binder-D9UEE1SM.d.ts → binder-Dq_AZRbo.d.cts} +14 -2
  11. package/dist/{chunk-KQCVZ2W7.cjs → chunk-B35DANTX.cjs} +303 -35
  12. package/dist/chunk-B35DANTX.cjs.map +1 -0
  13. package/dist/{chunk-2TVOEDRG.js → chunk-YCHJXSTC.js} +303 -36
  14. package/dist/chunk-YCHJXSTC.js.map +1 -0
  15. package/dist/index.cjs +19 -15
  16. package/dist/index.d.cts +4 -4
  17. package/dist/index.d.ts +4 -4
  18. package/dist/index.js +1 -1
  19. package/dist/persist.d.cts +1 -1
  20. package/dist/persist.d.ts +1 -1
  21. package/dist/popout.d.cts +1 -1
  22. package/dist/popout.d.ts +1 -1
  23. package/dist/react.cjs +3 -3
  24. package/dist/react.d.cts +2 -2
  25. package/dist/react.d.ts +2 -2
  26. package/dist/react.js +1 -1
  27. package/dist/solid.cjs +3 -3
  28. package/dist/solid.d.cts +2 -2
  29. package/dist/solid.d.ts +2 -2
  30. package/dist/solid.js +1 -1
  31. package/dist/svelte.cjs +3 -3
  32. package/dist/svelte.d.cts +2 -2
  33. package/dist/svelte.d.ts +2 -2
  34. package/dist/svelte.js +1 -1
  35. package/dist/themes/light.css +202 -0
  36. package/dist/themes/retro.css +152 -0
  37. package/dist/{types-Bkrq2djr.d.cts → types-DdCtDHgt.d.cts} +23 -1
  38. package/dist/{types-Bkrq2djr.d.ts → types-DdCtDHgt.d.ts} +23 -1
  39. package/dist/vue.cjs +3 -3
  40. package/dist/vue.d.cts +2 -2
  41. package/dist/vue.d.ts +2 -2
  42. package/dist/vue.js +1 -1
  43. package/package.json +34 -7
  44. package/dist/chunk-2TVOEDRG.js.map +0 -1
  45. package/dist/chunk-KQCVZ2W7.cjs.map +0 -1
package/README.md CHANGED
@@ -14,13 +14,17 @@
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
17
+ - ⚛️ **Official adapters** — `@surdeddd/wmkit/react`, `/vue`, `/svelte`, `/solid`, `/angular`, all thin sugar over one core
18
18
  - ⊞ **Snap zones** — halves, quarters 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
+ - 🗂️ **Named layouts & arrange** — save/load desktop snapshots, `cascade`/`tile` in one call
19
22
  - ⌨️ **Accessible** — keyboard move/resize, F6 window cycling, focus-trapped modals, `aria-live` announcements
20
23
  - ⚡ **Fast** — `transform`-only positioning, rAF-batched pointer input, structural sharing; 50 windows drag at 60fps
21
24
  - 💾 **Persistence** — one call to serialize the desktop, one call to restore it
25
+ - 🎨 **Three themes** — dark glass, light glass and Win98 retro, or bring your own CSS
22
26
  - 🖼️ **Popout** *(experimental)* — send a window into Document Picture-in-Picture
23
- - 📦 **Zero dependencies**, strict TypeScript, ESM + CJS, ~9 kB gzip core
27
+ - 📦 **Zero dependencies**, strict TypeScript, ESM + CJS, ~9.3 kB brotli core
24
28
 
25
29
  ## Install
26
30
 
@@ -190,6 +194,41 @@ function Desktop() {
190
194
  }
191
195
  ```
192
196
 
197
+ ## Angular
198
+
199
+ ```ts
200
+ import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
201
+ import { useWindowManager, createDesktop, useWmState } from '@surdeddd/wmkit/angular'
202
+
203
+ @Component({
204
+ selector: 'app-desktop',
205
+ standalone: true,
206
+ template: `
207
+ <div #desktop style="position: relative; height: 100vh">
208
+ <section #hello>
209
+ <header data-wm-drag><span data-wm-title>Hello</span></header>
210
+ <div data-wm-content>signals inside</div>
211
+ </section>
212
+ </div>
213
+ `,
214
+ })
215
+ export class DesktopComponent implements AfterViewInit {
216
+ wm = useWindowManager()
217
+ dk = createDesktop(this.wm)
218
+ state = useWmState(this.wm)
219
+ @ViewChild('desktop') desktopRef!: ElementRef<HTMLElement>
220
+ @ViewChild('hello') helloRef!: ElementRef<HTMLElement>
221
+
222
+ ngAfterViewInit(): void {
223
+ this.wm.open({ id: 'hello', title: 'Hello' })
224
+ this.dk.desktop(this.desktopRef.nativeElement)
225
+ this.dk.window('hello')(this.helloRef.nativeElement)
226
+ }
227
+ }
228
+ ```
229
+
230
+ `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.
231
+
193
232
  ## Snap zones in action
194
233
 
195
234
  [![Snap zones — window tiled to the left half](https://raw.githubusercontent.com/Surdeddd/wmkit/main/.github/assets/snap.png)](https://surdeddd.github.io/wmkit/)
@@ -211,6 +250,7 @@ interface ManagerOptions {
211
250
  cascadeOffset?: number // auto-position step for new windows (default 32)
212
251
  cascadeOrigin?: { x: number; y: number }
213
252
  idPrefix?: string
253
+ historyLimit?: number // undo/redo depth (default 50, 0 disables history)
214
254
  }
215
255
  ```
216
256
 
@@ -228,6 +268,10 @@ Manager methods:
228
268
  | `update(id, patch)` | title, layer, min/max size, per-window flags, `meta` |
229
269
  | `setViewport(size)` | re-derives maximized/snapped bounds, clamps the rest |
230
270
  | `serialize()` / `hydrate(data)` | JSON-safe snapshot of the whole desktop |
271
+ | `undo()` / `redo()` / `canUndo()` / `canRedo()` / `clearHistory()` | every mutation is one step; a whole drag or resize collapses into a single entry |
272
+ | `saveLayout(name)` / `loadLayout(name)` / `deleteLayout(name)` / `layoutNames()` | named desktop snapshots; `getLayout`/`setLayout` for external storage |
273
+ | `arrange('cascade' \| 'tile')` | cascade staggers restored sizes, tile fills the viewport in a grid |
274
+ | `minimizeAll()` / `restoreAll()` | bulk stage switches in one history step |
231
275
  | `subscribe(fn)` / `on(event, fn)` | granular events: `open, close, focus, move, resize, stage, update, order, modalblocked` |
232
276
  | `batch(fn)` | coalesce many operations into one `change` notification |
233
277
 
@@ -243,7 +287,10 @@ interface DesktopOptions {
243
287
  keyboard?: boolean | { moveStep?: number; cycle?: boolean }
244
288
  announce?: boolean | Partial<AnnouncerMessages> // localize screen-reader strings here
245
289
  autoViewport?: boolean // ResizeObserver → wm.setViewport (default true)
290
+ magnetism?: boolean | { threshold?: number } // edge-align to neighbours + viewport while dragging (default on, 8 px / 12 px coarse)
291
+ hitAreas?: { edge?: number; corner?: number } // resize handle thickness (auto-doubles on touch)
246
292
  minimizeTarget?: (win: WindowState) => Element | null // FLIP ghost target on minimize
293
+ onTitlebarContextMenu?: (win: WindowState, event: MouseEvent) => void // right-click / long-press hook for your own menu
247
294
  }
248
295
  ```
249
296
 
@@ -277,6 +324,8 @@ Moves a window's content into a [Document Picture-in-Picture](https://developer.
277
324
  }
278
325
  ```
279
326
 
327
+ 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.
328
+
280
329
  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
330
 
282
331
  ## SSR
@@ -289,7 +338,7 @@ The core never touches `window`/`document` — create managers and even `hydrate
289
338
  | --- | --- | --- | --- | --- | --- |
290
339
  | Maintained | ✓ 2026 | ✗ since 2023 | ✗ since 2022 | ✓ | ✓ |
291
340
  | Headless core | ✓ | ✗ | ✗ | ~ own UI | ✓ |
292
- | Official adapters | React·Vue·Svelte·Solid | community | ✗ | React·Vue·Angular | via Ark UI |
341
+ | Official adapters | React·Vue·Svelte·Solid·Angular | community | ✗ | React·Vue·Angular | via Ark UI |
293
342
  | Multi-window (z-order, taskbar, modals) | ✓ | partial | partial | dock groups | ✗ single panel |
294
343
  | Snap zones + preview | ✓ | ✗ | ✗ | — | ✗ |
295
344
  | Keyboard + screen reader | ✓ | ✗ | ✗ | partial | partial |
@@ -301,8 +350,9 @@ The core never touches `window`/`document` — create managers and even `hydrate
301
350
 
302
351
  ## Quality
303
352
 
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
353
+ - 172 unit tests, **100%** line/branch/function/statement coverage on the core state machine and persistence
354
+ - 178+ Playwright scenarios on Chromium, WebKit and mobile emulation: drag, 8-way resize, snap, magnetism, undo after drag, keyboard, touch, persistence across reloads, 50-window stress, modal traps, axe accessibility scans, visual regression screenshots
355
+ - 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
356
  - `publint` + `@arethetypeswrong/cli` validate the published package, `size-limit` guards bundle budgets
307
357
 
308
358
  ## Development
package/README.ru.md CHANGED
@@ -10,13 +10,17 @@
10
10
 
11
11
  - 🪟 **Полный жизненный цикл окна** — открытие, закрытие, фокус, сворачивание, разворачивание, восстановление, drag, ресайз в 8 направлениях
12
12
  - 🧠 **Headless-ядро** — сериализуемая стейт-машина плюс DOM-контроллер; своя разметка или готовая стеклянная тема
13
- - ⚛️ **Родные адаптеры** — `@surdeddd/wmkit/react`, `@surdeddd/wmkit/vue`, `@surdeddd/wmkit/svelte`, `@surdeddd/wmkit/solid`, тонкий сахар над одним ядром
13
+ - ⚛️ **Родные адаптеры** — `@surdeddd/wmkit/react`, `/vue`, `/svelte`, `/solid`, `/angular`, тонкий сахар над одним ядром
14
14
  - ⊞ **Snap-зоны** — половины, четверти и максимизация от верхнего края с живым превью
15
+ - 🧲 **Магнетизм** — края окна прилипают к соседям и вьюпорту при перетаскивании
16
+ - ↩️ **Undo/redo** — каждая мутация = один шаг; целый drag схлопывается в одну запись истории
17
+ - 🗂️ **Именованные layout'ы и arrange** — снапшоты рабочего стола, `cascade`/`tile` одним вызовом
15
18
  - ⌨️ **Доступность** — move/resize с клавиатуры, цикл окон по F6, focus-trap в модалках, `aria-live`-анонсы
16
19
  - ⚡ **Производительность** — позиционирование только через `transform`, rAF-батчинг ввода, structural sharing; 50 окон таскаются на 60fps
17
20
  - 💾 **Персист** — один вызов сериализует рабочий стол, один — восстанавливает
21
+ - 🎨 **Три темы** — тёмное стекло, светлое стекло и Win98-ретро, либо полностью свой CSS
18
22
  - 🖼️ **Popout** *(experimental)* — вынос окна в Document Picture-in-Picture
19
- - 📦 **Ноль зависимостей**, строгий TypeScript, ESM + CJS, ~9 kB gzip
23
+ - 📦 **Ноль зависимостей**, строгий TypeScript, ESM + CJS, ~9.3 kB brotli
20
24
 
21
25
  ## Установка
22
26
 
@@ -68,7 +72,7 @@ desktop.attachWindow(win.id, el, { removeOnClose: true })
68
72
 
69
73
  ## Адаптеры
70
74
 
71
- Примеры для React, Vue, Svelte и Solid — в [английском README](./README.md#react) и на [лендинге](https://surdeddd.github.io/wmkit/) (табы «Фреймворки»). Принцип один: контент окна живёт в дереве вашего фреймворка, никакого innerHTML.
75
+ Примеры для React, Vue, Svelte, Solid и Angular — в [английском README](./README.md#react) и на [лендинге](https://surdeddd.github.io/wmkit/) (табы «Фреймворки»). Принцип один: контент окна живёт в дереве вашего фреймворка, никакого innerHTML.
72
76
 
73
77
  ## API ядра — кратко
74
78
 
@@ -85,12 +89,18 @@ wm.hydrate(json)
85
89
 
86
90
  wm.on('stage', ({ window, previous }) => console.log(previous, '→', window.stage))
87
91
  wm.batch(() => { /* много операций — одно уведомление */ })
92
+
93
+ wm.undo(); wm.redo() // история изменений, drag = одна запись (historyLimit, default 50)
94
+ wm.saveLayout('работа'); wm.loadLayout('работа') // именованные снапшоты рабочего стола
95
+ wm.arrange('tile') // или 'cascade'; плюс minimizeAll() / restoreAll()
88
96
  ```
89
97
 
90
98
  Слои: `normal` < `floating` (always-on-top) < `modal`. Модалка блокирует фокус нижних окон (попытка — событие `modalblocked` и flash-анимация), Tab заперт внутри.
91
99
 
92
100
  Клавиатура по умолчанию: стрелки двигают сфокусированное окно (16 px), `Alt` — шаг 1 px, `Shift+стрелки` — ресайз, `F6`/`Shift+F6` — цикл по окнам, `Escape` отменяет активный drag/resize.
93
101
 
102
+ Магнетизм включён из коробки (порог 8 px, на тач-устройствах 12 px): `attachDesktop(wm, el, { magnetism: { threshold: 16 } })` или `magnetism: false`. Свой контекст-меню тайтлбара — через `onTitlebarContextMenu(win, event)` (правый клик и long-press на таче).
103
+
94
104
  ### Персист
95
105
 
96
106
  ```js
@@ -111,14 +121,17 @@ if (isPopoutSupported()) await popout(wm, 'docs', contentElement)
111
121
 
112
122
  Подключите `@surdeddd/wmkit/themes/glass.css` и переопределяйте CSS-переменные (`--wm-radius`, `--wm-bg`, `--wm-accent`, …) — или не подключайте ничего и стилизуйте `data-wm-stage`, `data-wm-focused`, `data-wm-dragging`, `[hidden]` сами.
113
123
 
124
+ Ещё две готовые темы: `themes/light.css` (светлое стекло) и `themes/retro.css` (Win98-ностальгия). Все три стилизуют одни и те же `data-wm-*` атрибуты — переключение = замена одного импорта.
125
+
114
126
  ## SSR
115
127
 
116
128
  Ядро не трогает `window`/`document`: менеджер можно создавать и гидрейтить на сервере, `attachDesktop` вызывается после маунта. `persist` тихо выключается без доступного storage.
117
129
 
118
130
  ## Качество
119
131
 
120
- - 121 юнит-тест, **100%** покрытие стейт-машины и persist по строкам/веткам/функциям
121
- - 160+ Playwright-сценариев на Chromium, WebKit и мобильной эмуляции: drag, ресайз во все стороны, снэп, клавиатура, touch, персист через перезагрузку, стресс на 50 окон, модальные ловушки, axe-аудиты доступности
132
+ - 172 юнит-теста, **100%** покрытие стейт-машины и persist по строкам/веткам/функциям
133
+ - 178+ Playwright-сценариев на Chromium, WebKit и мобильной эмуляции: drag, ресайз во все стороны, снэп, магнетизм, undo после drag, клавиатура, touch, персист через перезагрузку, стресс на 50 окон, модальные ловушки, axe-аудиты доступности, визуальная регрессия по скриншотам
134
+ - перф-бенчмарки в CI на каждый push (`vitest bench`): 1 000 окон открываются за ~150 мс, move среди 50 окон ~1.2 мкс, полный undo/redo-проход на 100 шагов ~52 мкс
122
135
  - `publint` + `@arethetypeswrong/cli` проверяют валидность пакета, `size-limit` следит за бюджетами
123
136
 
124
137
  ## Лицензия
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ var chunkB35DANTX_cjs = require('./chunk-B35DANTX.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 = chunkB35DANTX_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 = chunkB35DANTX_cjs.createDesktopBinder(wm, options);
32
+ return {
33
+ binder,
34
+ desktop(element) {
35
+ const unbind = binder.bindDesktop(element);
36
+ onDestroy(unbind);
37
+ },
38
+ window(id, windowOptions) {
39
+ return (element) => {
40
+ const unbind = binder.bindWindow(id, element, windowOptions);
41
+ onDestroy(unbind);
42
+ };
43
+ }
44
+ };
45
+ }
46
+
47
+ exports.createDesktop = createDesktop;
48
+ exports.useWindowManager = useWindowManager;
49
+ exports.useWmState = useWmState;
50
+ exports.useWmWindow = useWmWindow;
51
+ //# sourceMappingURL=angular.cjs.map
52
+ //# 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,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 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-DdCtDHgt.cjs';
3
+ import { D as DesktopBinder, W as WindowAttachOptions, a as DesktopOptions } from './binder-Dq_AZRbo.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-DdCtDHgt.js';
3
+ import { D as DesktopBinder, W as WindowAttachOptions, a as DesktopOptions } from './binder-0Oku22Nc.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,47 @@
1
+ import { createWindowManager, createDesktopBinder } from './chunk-YCHJXSTC.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
+ return {
31
+ binder,
32
+ desktop(element) {
33
+ const unbind = binder.bindDesktop(element);
34
+ onDestroy(unbind);
35
+ },
36
+ window(id, windowOptions) {
37
+ return (element) => {
38
+ const unbind = binder.bindWindow(id, element, windowOptions);
39
+ onDestroy(unbind);
40
+ };
41
+ }
42
+ };
43
+ }
44
+
45
+ export { createDesktop, useWindowManager, useWmState, useWmWindow };
46
+ //# sourceMappingURL=angular.js.map
47
+ //# 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,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 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,4 +1,4 @@
1
- import { B as Bounds, f as Size, g as SnapZone, W as WindowManager, b as WindowState } from './types-Bkrq2djr.cjs';
1
+ import { B as Bounds, f as Size, g as SnapZone, W as WindowManager, b as WindowState } from './types-DdCtDHgt.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;
@@ -10,6 +10,13 @@ interface SnapDetectOptions {
10
10
  cornerSize?: number;
11
11
  }
12
12
  declare function detectSnapZone(x: number, y: number, viewport: Size, options?: SnapDetectOptions): SnapZone | null;
13
+ interface MagnetResult {
14
+ x: number;
15
+ y: number;
16
+ snappedX: boolean;
17
+ snappedY: boolean;
18
+ }
19
+ declare function magnetize(bounds: Bounds, targets: readonly Bounds[], threshold: number): MagnetResult;
13
20
 
14
21
  interface AnnouncerMessages {
15
22
  opened(title: string): string;
@@ -40,13 +47,18 @@ interface HitAreaOptions {
40
47
  edge?: number;
41
48
  corner?: number;
42
49
  }
50
+ interface MagnetismOptions {
51
+ threshold?: number;
52
+ }
43
53
  interface DesktopOptions {
44
54
  snap?: boolean | DesktopSnapOptions;
45
55
  keyboard?: boolean | DesktopKeyboardOptions;
46
56
  announce?: boolean | Partial<AnnouncerMessages>;
47
57
  autoViewport?: boolean;
48
58
  hitAreas?: HitAreaOptions;
59
+ magnetism?: boolean | MagnetismOptions;
49
60
  minimizeTarget?: (window: WindowState) => Element | null;
61
+ onTitlebarContextMenu?: (window: WindowState, event: MouseEvent) => void;
50
62
  }
51
63
  interface WindowAttachOptions {
52
64
  handle?: HTMLElement | string;
@@ -68,4 +80,4 @@ interface DesktopBinder {
68
80
  }
69
81
  declare function createDesktopBinder(wm: WindowManager, options?: DesktopOptions): DesktopBinder;
70
82
 
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 };
83
+ 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, boundsEqual as g, clamp as h, clampSize as i, clampToViewport as j, createAnnouncer as k, createDesktopBinder as l, defaultMessages as m, detectSnapZone as n, magnetize as o, zoneBounds as z };
@@ -1,4 +1,4 @@
1
- import { B as Bounds, f as Size, g as SnapZone, W as WindowManager, b as WindowState } from './types-Bkrq2djr.js';
1
+ import { B as Bounds, f as Size, g as SnapZone, W as WindowManager, b as WindowState } from './types-DdCtDHgt.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;
@@ -10,6 +10,13 @@ interface SnapDetectOptions {
10
10
  cornerSize?: number;
11
11
  }
12
12
  declare function detectSnapZone(x: number, y: number, viewport: Size, options?: SnapDetectOptions): SnapZone | null;
13
+ interface MagnetResult {
14
+ x: number;
15
+ y: number;
16
+ snappedX: boolean;
17
+ snappedY: boolean;
18
+ }
19
+ declare function magnetize(bounds: Bounds, targets: readonly Bounds[], threshold: number): MagnetResult;
13
20
 
14
21
  interface AnnouncerMessages {
15
22
  opened(title: string): string;
@@ -40,13 +47,18 @@ interface HitAreaOptions {
40
47
  edge?: number;
41
48
  corner?: number;
42
49
  }
50
+ interface MagnetismOptions {
51
+ threshold?: number;
52
+ }
43
53
  interface DesktopOptions {
44
54
  snap?: boolean | DesktopSnapOptions;
45
55
  keyboard?: boolean | DesktopKeyboardOptions;
46
56
  announce?: boolean | Partial<AnnouncerMessages>;
47
57
  autoViewport?: boolean;
48
58
  hitAreas?: HitAreaOptions;
59
+ magnetism?: boolean | MagnetismOptions;
49
60
  minimizeTarget?: (window: WindowState) => Element | null;
61
+ onTitlebarContextMenu?: (window: WindowState, event: MouseEvent) => void;
50
62
  }
51
63
  interface WindowAttachOptions {
52
64
  handle?: HTMLElement | string;
@@ -68,4 +80,4 @@ interface DesktopBinder {
68
80
  }
69
81
  declare function createDesktopBinder(wm: WindowManager, options?: DesktopOptions): DesktopBinder;
70
82
 
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 };
83
+ 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, boundsEqual as g, clamp as h, clampSize as i, clampToViewport as j, createAnnouncer as k, createDesktopBinder as l, defaultMessages as m, detectSnapZone as n, magnetize as o, zoneBounds as z };