@surdeddd/wmkit 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -16
- package/README.ru.md +154 -10
- package/dist/angular.cjs +4 -3
- package/dist/angular.cjs.map +1 -1
- package/dist/angular.d.cts +2 -2
- package/dist/angular.d.ts +2 -2
- package/dist/angular.js +2 -1
- package/dist/angular.js.map +1 -1
- package/dist/{binder-0Oku22Nc.d.ts → binder-B2A2IZGh.d.ts} +7 -2
- package/dist/{binder-Dq_AZRbo.d.cts → binder-Dl07SXIM.d.cts} +7 -2
- package/dist/{chunk-YCHJXSTC.js → chunk-OOEDFA7K.js} +404 -135
- package/dist/chunk-OOEDFA7K.js.map +1 -0
- package/dist/{chunk-B35DANTX.cjs → chunk-TJ6QOOJ2.cjs} +405 -134
- package/dist/chunk-TJ6QOOJ2.cjs.map +1 -0
- package/dist/index.cjs +24 -16
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +1 -1
- package/dist/persist.cjs +22 -3
- package/dist/persist.cjs.map +1 -1
- package/dist/persist.d.cts +3 -1
- package/dist/persist.d.ts +3 -1
- package/dist/persist.js +22 -3
- package/dist/persist.js.map +1 -1
- package/dist/popout.cjs +3 -4
- package/dist/popout.cjs.map +1 -1
- package/dist/popout.d.cts +1 -1
- package/dist/popout.d.ts +1 -1
- package/dist/popout.js +3 -4
- package/dist/popout.js.map +1 -1
- package/dist/react.cjs +4 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/dist/solid.cjs +4 -3
- package/dist/solid.cjs.map +1 -1
- package/dist/solid.d.cts +2 -2
- package/dist/solid.d.ts +2 -2
- package/dist/solid.js +2 -1
- package/dist/solid.js.map +1 -1
- package/dist/svelte.cjs +8 -5
- package/dist/svelte.cjs.map +1 -1
- package/dist/svelte.d.cts +2 -2
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +6 -3
- package/dist/svelte.js.map +1 -1
- package/dist/themes/retro.css +2 -0
- package/dist/{types-DdCtDHgt.d.cts → types-t6pyVYKN.d.cts} +21 -7
- package/dist/{types-DdCtDHgt.d.ts → types-t6pyVYKN.d.ts} +21 -7
- package/dist/vue.cjs +4 -3
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.d.cts +2 -2
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +2 -1
- package/dist/vue.js.map +1 -1
- package/package.json +7 -7
- package/dist/chunk-B35DANTX.cjs.map +0 -1
- package/dist/chunk-YCHJXSTC.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Headless window manager for the web.** Draggable, resizable, snappable windows with a taskbar model, keyboard accessibility and state persistence — for vanilla JS and every major framework.
|
|
4
4
|
|
|
5
|
-
[Русская версия](./README.ru.md) · [Live demo](https://wmkit.vercel.app) · [Mirror (Pages)](https://surdeddd.github.io/wmkit/) · [GitHub](https://github.com/Surdeddd/wmkit)
|
|
5
|
+
[Русская версия](./README.ru.md) · [Live demo](https://wmkit.vercel.app) · [Mirror (Pages)](https://surdeddd.github.io/wmkit/) · [Docs](./docs/README.md) · [GitHub](https://github.com/Surdeddd/wmkit)
|
|
6
6
|
|
|
7
7
|
[](https://github.com/Surdeddd/wmkit/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/@surdeddd/wmkit)
|
|
@@ -15,16 +15,17 @@
|
|
|
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
17
|
- ⚛️ **Official adapters** — `@surdeddd/wmkit/react`, `/vue`, `/svelte`, `/solid`, `/angular`, all thin sugar over one core
|
|
18
|
-
- ⊞ **Snap zones** — halves, quarters and drag-to-top maximize with a live preview
|
|
18
|
+
- ⊞ **Snap zones** — halves, quarters, thirds and drag-to-top maximize with a live preview
|
|
19
19
|
- 🧲 **Magnetism** — window edges align to neighbours and the viewport while dragging
|
|
20
20
|
- ↩️ **Undo/redo** — every mutation is one step; a whole drag collapses into a single history entry
|
|
21
|
-
- 🗂️ **
|
|
22
|
-
-
|
|
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
|
|
23
24
|
- ⚡ **Fast** — `transform`-only positioning, rAF-batched pointer input, structural sharing; 50 windows drag at 60fps
|
|
24
|
-
- 💾 **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
|
|
25
26
|
- 🎨 **Three themes** — dark glass, light glass and Win98 retro, or bring your own CSS
|
|
26
27
|
- 🖼️ **Popout** *(experimental)* — send a window into Document Picture-in-Picture
|
|
27
|
-
- 📦 **Zero dependencies**, strict TypeScript, ESM + CJS, ~
|
|
28
|
+
- 📦 **Zero dependencies**, strict TypeScript, ESM + CJS, ~10.7 kB brotli core
|
|
28
29
|
|
|
29
30
|
## Install
|
|
30
31
|
|
|
@@ -231,7 +232,7 @@ export class DesktopComponent implements AfterViewInit {
|
|
|
231
232
|
|
|
232
233
|
## Snap zones in action
|
|
233
234
|
|
|
234
|
-
[](https://surdeddd.github.io/wmkit/)
|
|
235
236
|
|
|
236
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.
|
|
237
238
|
|
|
@@ -251,6 +252,7 @@ interface ManagerOptions {
|
|
|
251
252
|
cascadeOrigin?: { x: number; y: number }
|
|
252
253
|
idPrefix?: string
|
|
253
254
|
historyLimit?: number // undo/redo depth (default 50, 0 disables history)
|
|
255
|
+
workspace?: number // initial virtual desktop (default 0)
|
|
254
256
|
}
|
|
255
257
|
```
|
|
256
258
|
|
|
@@ -262,13 +264,16 @@ Manager methods:
|
|
|
262
264
|
| `close(id)` / `closeAll()` | focus moves to the next eligible window |
|
|
263
265
|
| `focus(id)` / `blur()` / `cycleFocus(dir?)` | focusing a minimized window restores it; modals block focus below them |
|
|
264
266
|
| `minimize(id)` / `maximize(id)` / `restore(id)` / `toggleMaximize(id)` | restore returns to the pre-minimize stage, including maximized/snapped |
|
|
265
|
-
| `snap(id, zone)` | `'left' \| 'right' \| 'top' \| 'bottom' \| 'top-left' \|
|
|
266
|
-
| `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 |
|
|
267
270
|
| `restoreTo(id, bounds)` | used for drag-off-snap; stage → `normal` at explicit bounds |
|
|
268
|
-
| `update(id, patch)` | title, layer, min/max size, per-window flags, `meta` |
|
|
269
|
-
| `setViewport(size)` | re-derives maximized/snapped bounds, clamps the rest |
|
|
270
|
-
| `
|
|
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 |
|
|
271
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 |
|
|
272
277
|
| `saveLayout(name)` / `loadLayout(name)` / `deleteLayout(name)` / `layoutNames()` | named desktop snapshots; `getLayout`/`setLayout` for external storage |
|
|
273
278
|
| `arrange('cascade' \| 'tile')` | cascade staggers restored sizes, tile fills the viewport in a grid |
|
|
274
279
|
| `minimizeAll()` / `restoreAll()` | bulk stage switches in one history step |
|
|
@@ -284,7 +289,7 @@ DOM controller: pointer drag with capture (touch/pen included), 8-direction resi
|
|
|
284
289
|
```ts
|
|
285
290
|
interface DesktopOptions {
|
|
286
291
|
snap?: boolean | { threshold?: number; cornerSize?: number; preview?: boolean; topEdge?: 'maximize' | 'top' | 'none' }
|
|
287
|
-
keyboard?: boolean | { moveStep?: number; cycle?: boolean }
|
|
292
|
+
keyboard?: boolean | { moveStep?: number; cycle?: boolean; snapShortcuts?: boolean; historyShortcuts?: boolean }
|
|
288
293
|
announce?: boolean | Partial<AnnouncerMessages> // localize screen-reader strings here
|
|
289
294
|
autoViewport?: boolean // ResizeObserver → wm.setViewport (default true)
|
|
290
295
|
magnetism?: boolean | { threshold?: number } // edge-align to neighbours + viewport while dragging (default on, 8 px / 12 px coarse)
|
|
@@ -294,7 +299,7 @@ interface DesktopOptions {
|
|
|
294
299
|
}
|
|
295
300
|
```
|
|
296
301
|
|
|
297
|
-
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).
|
|
298
303
|
|
|
299
304
|
### `persist(wm, options?)` — `@surdeddd/wmkit/persist`
|
|
300
305
|
|
|
@@ -307,6 +312,15 @@ store.clear()
|
|
|
307
312
|
|
|
308
313
|
Storage defaults to `localStorage` (probed safely — SSR and private-mode friendly) and accepts any `getItem/setItem/removeItem` implementation.
|
|
309
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
|
+
|
|
310
324
|
### `popout(wm, id, contentEl, options?)` — `@surdeddd/wmkit/popout` *(experimental)*
|
|
311
325
|
|
|
312
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()`.
|
|
@@ -332,6 +346,18 @@ Skip the import entirely and the library stays headless: state attributes (`data
|
|
|
332
346
|
|
|
333
347
|
The core never touches `window`/`document` — create managers and even `hydrate()` state on the server, then call `attachDesktop` after mount. `persist` no-ops without usable storage.
|
|
334
348
|
|
|
349
|
+
## Documentation
|
|
350
|
+
|
|
351
|
+
This README is the tour. The reference lives in [`docs/`](./docs/README.md):
|
|
352
|
+
|
|
353
|
+
| Page | What is in it |
|
|
354
|
+
| --- | --- |
|
|
355
|
+
| [API reference](./docs/api.md) | every export, option, method, event and type |
|
|
356
|
+
| [Adapters](./docs/adapters.md) | complete React, Vue, Svelte, Solid and Angular integrations |
|
|
357
|
+
| [Theming](./docs/theming.md) | `data-wm-*` contract, CSS variables, writing a theme from scratch |
|
|
358
|
+
| [Recipes](./docs/recipes.md) | taskbar, modals, persistence, workspaces, SSR, testing, performance |
|
|
359
|
+
| [Browser support](./docs/browser-support.md) | baselines per entry point and what degrades where |
|
|
360
|
+
|
|
335
361
|
## Comparison
|
|
336
362
|
|
|
337
363
|
| | wmkit | WinBox | jsPanel4 | Dockview | Zag floating-panel |
|
|
@@ -350,8 +376,8 @@ The core never touches `window`/`document` — create managers and even `hydrate
|
|
|
350
376
|
|
|
351
377
|
## Quality
|
|
352
378
|
|
|
353
|
-
-
|
|
354
|
-
-
|
|
379
|
+
- 216 unit tests, **100%** line/branch/function/statement coverage on the core state machine and persistence
|
|
380
|
+
- 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
|
|
355
381
|
- 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
|
|
356
382
|
- `publint` + `@arethetypeswrong/cli` validate the published package, `size-limit` guards bundle budgets
|
|
357
383
|
|
package/README.ru.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Headless оконный менеджер для веба.** Перетаскиваемые окна с ресайзом, снэпом, таскбаром, клавиатурной доступностью и персистом состояния — для vanilla JS и всех основных фреймворков.
|
|
4
4
|
|
|
5
|
-
[English version](./README.md) · [Живое демо](https://wmkit.vercel.app) · [Зеркало (Pages)](https://surdeddd.github.io/wmkit/) · [GitHub](https://github.com/Surdeddd/wmkit)
|
|
5
|
+
[English version](./README.md) · [Живое демо](https://wmkit.vercel.app) · [Зеркало (Pages)](https://surdeddd.github.io/wmkit/) · [Документация](./docs/README.md) · [GitHub](https://github.com/Surdeddd/wmkit)
|
|
6
6
|
|
|
7
7
|
[](https://surdeddd.github.io/wmkit/)
|
|
8
8
|
|
|
@@ -11,16 +11,17 @@
|
|
|
11
11
|
- 🪟 **Полный жизненный цикл окна** — открытие, закрытие, фокус, сворачивание, разворачивание, восстановление, drag, ресайз в 8 направлениях
|
|
12
12
|
- 🧠 **Headless-ядро** — сериализуемая стейт-машина плюс DOM-контроллер; своя разметка или готовая стеклянная тема
|
|
13
13
|
- ⚛️ **Родные адаптеры** — `@surdeddd/wmkit/react`, `/vue`, `/svelte`, `/solid`, `/angular`, тонкий сахар над одним ядром
|
|
14
|
-
- ⊞ **Snap-зоны** — половины,
|
|
14
|
+
- ⊞ **Snap-зоны** — половины, четверти, трети и максимизация от верхнего края с живым превью
|
|
15
15
|
- 🧲 **Магнетизм** — края окна прилипают к соседям и вьюпорту при перетаскивании
|
|
16
16
|
- ↩️ **Undo/redo** — каждая мутация = один шаг; целый drag схлопывается в одну запись истории
|
|
17
|
-
- 🗂️
|
|
18
|
-
-
|
|
17
|
+
- 🗂️ **Рабочие столы и layout'ы** — виртуальные столы, снапшоты, `cascade`/`tile` одним вызовом
|
|
18
|
+
- 🔒 **Фиксация пропорций** — окно с `aspectRatio` держит 16:9 или 4:3 при любом ресайзе
|
|
19
|
+
- ⌨️ **Доступность** — move/resize/snap с клавиатуры, цикл окон по F6, focus-trap в модалках, `aria-live`-анонсы
|
|
19
20
|
- ⚡ **Производительность** — позиционирование только через `transform`, rAF-батчинг ввода, structural sharing; 50 окон таскаются на 60fps
|
|
20
|
-
- 💾 **Персист** — один вызов сериализует рабочий стол, один —
|
|
21
|
+
- 💾 **Персист** — один вызов сериализует рабочий стол, один — восстанавливает, с версионированием и миграциями
|
|
21
22
|
- 🎨 **Три темы** — тёмное стекло, светлое стекло и Win98-ретро, либо полностью свой CSS
|
|
22
23
|
- 🖼️ **Popout** *(experimental)* — вынос окна в Document Picture-in-Picture
|
|
23
|
-
- 📦 **Ноль зависимостей**, строгий TypeScript, ESM + CJS, ~
|
|
24
|
+
- 📦 **Ноль зависимостей**, строгий TypeScript, ESM + CJS, ~10.7 kB brotli
|
|
24
25
|
|
|
25
26
|
## Установка
|
|
26
27
|
|
|
@@ -72,7 +73,140 @@ desktop.attachWindow(win.id, el, { removeOnClose: true })
|
|
|
72
73
|
|
|
73
74
|
## Адаптеры
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
Каждый адаптер — тонкая обёртка над тем же ядром и тем же DOM-контроллером, около 60 строк. Они нужны только чтобы привязаться и отвязаться в нужный момент жизненного цикла компонента. Контент окна всегда живёт в дереве вашего фреймворка: никакого innerHTML, порталов и переноса узлов.
|
|
77
|
+
|
|
78
|
+
### React
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { useWindowManager, useDesktop, useWmState, useWmWindowRef } from '@surdeddd/wmkit/react'
|
|
82
|
+
|
|
83
|
+
function Desktop() {
|
|
84
|
+
const wm = useWindowManager()
|
|
85
|
+
const { ref, binder } = useDesktop(wm)
|
|
86
|
+
const state = useWmState(wm)
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div ref={ref} style={{ position: 'relative', height: '100vh' }}>
|
|
90
|
+
<button type="button" onClick={() => wm.open({ title: 'Окно' })}>открыть</button>
|
|
91
|
+
{state.order.map((id) => (
|
|
92
|
+
<Win key={id} binder={binder} win={state.windows[id]} />
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Win({ binder, win }) {
|
|
99
|
+
const ref = useWmWindowRef(binder, win.id, { removeOnClose: true })
|
|
100
|
+
return (
|
|
101
|
+
<section ref={ref}>
|
|
102
|
+
<header data-wm-drag><span data-wm-title>{win.title}</span></header>
|
|
103
|
+
<div data-wm-content>ваше дерево компонентов</div>
|
|
104
|
+
</section>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Vue
|
|
110
|
+
|
|
111
|
+
```vue
|
|
112
|
+
<script setup lang="ts">
|
|
113
|
+
import { ref } from 'vue'
|
|
114
|
+
import { useWindowManager, useDesktop, useWmWindowEl } from '@surdeddd/wmkit/vue'
|
|
115
|
+
|
|
116
|
+
const host = ref<HTMLElement>()
|
|
117
|
+
const panel = ref<HTMLElement>()
|
|
118
|
+
const wm = useWindowManager()
|
|
119
|
+
const binder = useDesktop(wm, host)
|
|
120
|
+
useWmWindowEl(binder, 'notes', panel)
|
|
121
|
+
wm.open({ id: 'notes', title: 'Заметки' })
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<template>
|
|
125
|
+
<div ref="host" style="position: relative; height: 100vh">
|
|
126
|
+
<section ref="panel">
|
|
127
|
+
<header data-wm-drag><span data-wm-title>Заметки</span></header>
|
|
128
|
+
<div data-wm-content>композаблы</div>
|
|
129
|
+
</section>
|
|
130
|
+
</div>
|
|
131
|
+
</template>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Svelte
|
|
135
|
+
|
|
136
|
+
```svelte
|
|
137
|
+
<script lang="ts">
|
|
138
|
+
import { createManager, createDesktop, wmWindowStore } from '@surdeddd/wmkit/svelte'
|
|
139
|
+
|
|
140
|
+
const wm = createManager()
|
|
141
|
+
const dk = createDesktop(wm)
|
|
142
|
+
wm.open({ id: 'notes', title: 'Заметки' })
|
|
143
|
+
const notes = wmWindowStore(wm, 'notes')
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<div use:dk.desktop style="position: relative; height: 100vh">
|
|
147
|
+
<section use:dk.window={{ id: 'notes' }}>
|
|
148
|
+
<header data-wm-drag><span data-wm-title>{$notes?.title}</span></header>
|
|
149
|
+
<div data-wm-content>сторы и экшены</div>
|
|
150
|
+
</section>
|
|
151
|
+
</div>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Solid
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
import { useWindowManager, createDesktop, useWmState } from '@surdeddd/wmkit/solid'
|
|
158
|
+
|
|
159
|
+
function Desktop() {
|
|
160
|
+
const wm = useWindowManager()
|
|
161
|
+
const dk = createDesktop(wm)
|
|
162
|
+
const state = useWmState(wm)
|
|
163
|
+
wm.open({ id: 'notes', title: 'Заметки' })
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div ref={dk.desktop} style={{ position: 'relative', height: '100vh' }}>
|
|
167
|
+
<section ref={dk.window('notes')}>
|
|
168
|
+
<header data-wm-drag><span data-wm-title>Заметки</span></header>
|
|
169
|
+
<div data-wm-content>{state().order.length} окон</div>
|
|
170
|
+
</section>
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Angular
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { Component, ElementRef, afterNextRender, viewChild } from '@angular/core'
|
|
180
|
+
import { useWindowManager, createDesktop, useWmState } from '@surdeddd/wmkit/angular'
|
|
181
|
+
|
|
182
|
+
@Component({
|
|
183
|
+
selector: 'app-desktop',
|
|
184
|
+
template: `
|
|
185
|
+
<div #host style="position: relative; height: 100vh">
|
|
186
|
+
<section #panel>
|
|
187
|
+
<header data-wm-drag><span data-wm-title>Заметки</span></header>
|
|
188
|
+
<div data-wm-content>{{ state().order.length }} окон</div>
|
|
189
|
+
</section>
|
|
190
|
+
</div>`,
|
|
191
|
+
})
|
|
192
|
+
export class DesktopComponent {
|
|
193
|
+
private host = viewChild.required<ElementRef>('host')
|
|
194
|
+
private panel = viewChild.required<ElementRef>('panel')
|
|
195
|
+
wm = useWindowManager()
|
|
196
|
+
state = useWmState(this.wm)
|
|
197
|
+
|
|
198
|
+
constructor() {
|
|
199
|
+
const dk = createDesktop(this.wm)
|
|
200
|
+
afterNextRender(() => {
|
|
201
|
+
dk.desktop(this.host().nativeElement)
|
|
202
|
+
this.wm.open({ id: 'notes', title: 'Заметки' })
|
|
203
|
+
dk.window('notes')(this.panel().nativeElement)
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Полные примеры с таскбаром, модалками и сигнатурами всех хуков — в [docs/adapters.md](./docs/adapters.md).
|
|
76
210
|
|
|
77
211
|
## API ядра — кратко
|
|
78
212
|
|
|
@@ -93,11 +227,16 @@ wm.batch(() => { /* много операций — одно уведомлен
|
|
|
93
227
|
wm.undo(); wm.redo() // история изменений, drag = одна запись (historyLimit, default 50)
|
|
94
228
|
wm.saveLayout('работа'); wm.loadLayout('работа') // именованные снапшоты рабочего стола
|
|
95
229
|
wm.arrange('tile') // или 'cascade'; плюс minimizeAll() / restoreAll()
|
|
230
|
+
|
|
231
|
+
wm.setWorkspace(1) // виртуальные столы: окна других столов скрыты и не фокусируются
|
|
232
|
+
wm.moveToWorkspace('docs', 1) // focus('docs') сам переключит стол обратно
|
|
233
|
+
wm.open({ id: 'video', aspectRatio: 16 / 9 }) // ресайз держит пропорцию
|
|
234
|
+
wm.center('video'); wm.sendToBack('docs')
|
|
96
235
|
```
|
|
97
236
|
|
|
98
237
|
Слои: `normal` < `floating` (always-on-top) < `modal`. Модалка блокирует фокус нижних окон (попытка — событие `modalblocked` и flash-анимация), Tab заперт внутри.
|
|
99
238
|
|
|
100
|
-
Клавиатура по умолчанию: стрелки двигают сфокусированное окно (16 px), `Alt` — шаг 1 px, `Shift+стрелки` — ресайз, `F6`/`Shift+F6` — цикл по окнам, `Escape` отменяет активный drag/resize.
|
|
239
|
+
Клавиатура по умолчанию: стрелки двигают сфокусированное окно (16 px), `Alt` — шаг 1 px, `Shift+стрелки` — ресайз, `Ctrl/⌘+Alt+←/→` — прилипание к половине, `Ctrl/⌘+Alt+↑/↓` — развернуть/свернуть, `Ctrl/⌘+Z` и `Ctrl/⌘+Shift+Z` — отмена и повтор, `F6`/`Shift+F6` — цикл по окнам, `Escape` отменяет активный drag/resize (и убирает его из истории).
|
|
101
240
|
|
|
102
241
|
Магнетизм включён из коробки (порог 8 px, на тач-устройствах 12 px): `attachDesktop(wm, el, { magnetism: { threshold: 16 } })` или `magnetism: false`. Свой контекст-меню тайтлбара — через `onTitlebarContextMenu(win, event)` (правый клик и long-press на таче).
|
|
103
242
|
|
|
@@ -106,6 +245,11 @@ wm.arrange('tile') // или 'cascade'; плюс minimizeAll() / r
|
|
|
106
245
|
```js
|
|
107
246
|
import { persist } from '@surdeddd/wmkit/persist'
|
|
108
247
|
persist(wm, { key: 'my-desktop' }) // авто-восстановление + debounce-сохранение
|
|
248
|
+
|
|
249
|
+
persist(wm, { // версия контракта + миграция старых данных
|
|
250
|
+
version: 2,
|
|
251
|
+
migrate: (state, from) => (from === 1 ? upgrade(state) : null),
|
|
252
|
+
})
|
|
109
253
|
```
|
|
110
254
|
|
|
111
255
|
### Popout (experimental)
|
|
@@ -129,8 +273,8 @@ if (isPopoutSupported()) await popout(wm, 'docs', contentElement)
|
|
|
129
273
|
|
|
130
274
|
## Качество
|
|
131
275
|
|
|
132
|
-
-
|
|
133
|
-
-
|
|
276
|
+
- 216 юнит-тестов, **100%** покрытие стейт-машины и persist по строкам/веткам/функциям
|
|
277
|
+
- 190+ Playwright-сценариев на Chromium, WebKit и мобильной эмуляции: drag, ресайз во все стороны, снэп, магнетизм, рабочие столы, undo после drag, клавиатура, touch, персист через перезагрузку, стресс на 50 окон, модальные ловушки, axe-аудиты доступности, визуальная регрессия по скриншотам
|
|
134
278
|
- перф-бенчмарки в CI на каждый push (`vitest bench`): 1 000 окон открываются за ~150 мс, move среди 50 окон ~1.2 мкс, полный undo/redo-проход на 100 шагов ~52 мкс
|
|
135
279
|
- `publint` + `@arethetypeswrong/cli` проверяют валидность пакета, `size-limit` следит за бюджетами
|
|
136
280
|
|
package/dist/angular.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkTJ6QOOJ2_cjs = require('./chunk-TJ6QOOJ2.cjs');
|
|
4
4
|
var core = require('@angular/core');
|
|
5
5
|
|
|
6
6
|
function onDestroy(dispose) {
|
|
@@ -13,7 +13,7 @@ function onDestroy(dispose) {
|
|
|
13
13
|
destroyRef?.onDestroy(dispose);
|
|
14
14
|
}
|
|
15
15
|
function useWindowManager(options) {
|
|
16
|
-
const wm =
|
|
16
|
+
const wm = chunkTJ6QOOJ2_cjs.createWindowManager(options);
|
|
17
17
|
onDestroy(() => wm.destroy());
|
|
18
18
|
return wm;
|
|
19
19
|
}
|
|
@@ -28,7 +28,8 @@ function useWmWindow(wm, id) {
|
|
|
28
28
|
return core.computed(() => state().windows[id]);
|
|
29
29
|
}
|
|
30
30
|
function createDesktop(wm, options) {
|
|
31
|
-
const binder =
|
|
31
|
+
const binder = chunkTJ6QOOJ2_cjs.createDesktopBinder(wm, options);
|
|
32
|
+
onDestroy(() => binder.destroy());
|
|
32
33
|
return {
|
|
33
34
|
binder,
|
|
34
35
|
desktop(element) {
|
package/dist/angular.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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"]}
|
package/dist/angular.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Signal } from '@angular/core';
|
|
2
|
-
import { W as WindowManager, M as ManagerOptions, a as ManagerState, b as WindowState } from './types-
|
|
3
|
-
import { D as DesktopBinder, W as WindowAttachOptions, a as DesktopOptions } from './binder-
|
|
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
4
|
|
|
5
5
|
declare function useWindowManager(options?: ManagerOptions): WindowManager;
|
|
6
6
|
declare function useWmState(wm: WindowManager): Signal<ManagerState>;
|
package/dist/angular.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Signal } from '@angular/core';
|
|
2
|
-
import { W as WindowManager, M as ManagerOptions, a as ManagerState, b as WindowState } from './types-
|
|
3
|
-
import { D as DesktopBinder, W as WindowAttachOptions, a as DesktopOptions } from './binder-
|
|
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
4
|
|
|
5
5
|
declare function useWindowManager(options?: ManagerOptions): WindowManager;
|
|
6
6
|
declare function useWmState(wm: WindowManager): Signal<ManagerState>;
|
package/dist/angular.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createWindowManager, createDesktopBinder } from './chunk-
|
|
1
|
+
import { createWindowManager, createDesktopBinder } from './chunk-OOEDFA7K.js';
|
|
2
2
|
import { signal, computed, inject, DestroyRef } from '@angular/core';
|
|
3
3
|
|
|
4
4
|
function onDestroy(dispose) {
|
|
@@ -27,6 +27,7 @@ function useWmWindow(wm, id) {
|
|
|
27
27
|
}
|
|
28
28
|
function createDesktop(wm, options) {
|
|
29
29
|
const binder = createDesktopBinder(wm, options);
|
|
30
|
+
onDestroy(() => binder.destroy());
|
|
30
31
|
return {
|
|
31
32
|
binder,
|
|
32
33
|
desktop(element) {
|
package/dist/angular.js.map
CHANGED
|
@@ -1 +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
|
+
{"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,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
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;
|
|
@@ -26,6 +27,7 @@ interface AnnouncerMessages {
|
|
|
26
27
|
maximized(title: string): string;
|
|
27
28
|
snapped(title: string, zone: string): string;
|
|
28
29
|
focused(title: string): string;
|
|
30
|
+
workspace(index: number): string;
|
|
29
31
|
}
|
|
30
32
|
declare const defaultMessages: AnnouncerMessages;
|
|
31
33
|
interface Announcer {
|
|
@@ -42,6 +44,8 @@ interface DesktopSnapOptions extends SnapDetectOptions {
|
|
|
42
44
|
interface DesktopKeyboardOptions {
|
|
43
45
|
moveStep?: number;
|
|
44
46
|
cycle?: boolean;
|
|
47
|
+
snapShortcuts?: boolean;
|
|
48
|
+
historyShortcuts?: boolean;
|
|
45
49
|
}
|
|
46
50
|
interface HitAreaOptions {
|
|
47
51
|
edge?: number;
|
|
@@ -77,7 +81,8 @@ interface DesktopBinder {
|
|
|
77
81
|
controller(): DesktopController | null;
|
|
78
82
|
bindDesktop(element: HTMLElement): () => void;
|
|
79
83
|
bindWindow(id: string, element: HTMLElement, options?: WindowAttachOptions): () => void;
|
|
84
|
+
destroy(): void;
|
|
80
85
|
}
|
|
81
86
|
declare function createDesktopBinder(wm: WindowManager, options?: DesktopOptions): DesktopBinder;
|
|
82
87
|
|
|
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,
|
|
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,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
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;
|
|
@@ -26,6 +27,7 @@ interface AnnouncerMessages {
|
|
|
26
27
|
maximized(title: string): string;
|
|
27
28
|
snapped(title: string, zone: string): string;
|
|
28
29
|
focused(title: string): string;
|
|
30
|
+
workspace(index: number): string;
|
|
29
31
|
}
|
|
30
32
|
declare const defaultMessages: AnnouncerMessages;
|
|
31
33
|
interface Announcer {
|
|
@@ -42,6 +44,8 @@ interface DesktopSnapOptions extends SnapDetectOptions {
|
|
|
42
44
|
interface DesktopKeyboardOptions {
|
|
43
45
|
moveStep?: number;
|
|
44
46
|
cycle?: boolean;
|
|
47
|
+
snapShortcuts?: boolean;
|
|
48
|
+
historyShortcuts?: boolean;
|
|
45
49
|
}
|
|
46
50
|
interface HitAreaOptions {
|
|
47
51
|
edge?: number;
|
|
@@ -77,7 +81,8 @@ interface DesktopBinder {
|
|
|
77
81
|
controller(): DesktopController | null;
|
|
78
82
|
bindDesktop(element: HTMLElement): () => void;
|
|
79
83
|
bindWindow(id: string, element: HTMLElement, options?: WindowAttachOptions): () => void;
|
|
84
|
+
destroy(): void;
|
|
80
85
|
}
|
|
81
86
|
declare function createDesktopBinder(wm: WindowManager, options?: DesktopOptions): DesktopBinder;
|
|
82
87
|
|
|
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,
|
|
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 };
|