@surdeddd/wmkit 0.7.1 → 0.8.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 (78) hide show
  1. package/README.md +37 -2
  2. package/README.ru.md +37 -2
  3. package/dist/angular.cjs +4 -3
  4. package/dist/angular.cjs.map +1 -1
  5. package/dist/angular.d.cts +2 -1
  6. package/dist/angular.d.ts +2 -1
  7. package/dist/angular.js +2 -1
  8. package/dist/angular.js.map +1 -1
  9. package/dist/binder-B625OrjD.d.cts +13 -0
  10. package/dist/binder-CpGY8Wye.d.ts +13 -0
  11. package/dist/chunk-5XVIC5LH.js +140 -0
  12. package/dist/chunk-5XVIC5LH.js.map +1 -0
  13. package/dist/chunk-BDHFXEJN.cjs +149 -0
  14. package/dist/chunk-BDHFXEJN.cjs.map +1 -0
  15. package/dist/{chunk-2VRCYMKH.cjs → chunk-DINVOL6Z.cjs} +63 -178
  16. package/dist/chunk-DINVOL6Z.cjs.map +1 -0
  17. package/dist/{chunk-WOCAQEM6.js → chunk-MGWCKHTZ.js} +44 -151
  18. package/dist/chunk-MGWCKHTZ.js.map +1 -0
  19. package/dist/devtools.cjs +330 -0
  20. package/dist/devtools.cjs.map +1 -0
  21. package/dist/devtools.d.cts +43 -0
  22. package/dist/devtools.d.ts +43 -0
  23. package/dist/devtools.js +326 -0
  24. package/dist/devtools.js.map +1 -0
  25. package/dist/gestures.cjs +219 -0
  26. package/dist/gestures.cjs.map +1 -0
  27. package/dist/gestures.d.cts +19 -0
  28. package/dist/gestures.d.ts +19 -0
  29. package/dist/gestures.js +215 -0
  30. package/dist/gestures.js.map +1 -0
  31. package/dist/index.cjs +35 -34
  32. package/dist/index.d.cts +3 -2
  33. package/dist/index.d.ts +3 -2
  34. package/dist/index.js +2 -1
  35. package/dist/react.cjs +4 -3
  36. package/dist/react.cjs.map +1 -1
  37. package/dist/react.d.cts +2 -1
  38. package/dist/react.d.ts +2 -1
  39. package/dist/react.js +2 -1
  40. package/dist/react.js.map +1 -1
  41. package/dist/{binder-DvQFmO3K.d.ts → shared-BBXgEeIa.d.ts} +22 -8
  42. package/dist/{binder-_L3Iww0u.d.cts → shared-DU_87nY9.d.cts} +22 -8
  43. package/dist/solid.cjs +4 -3
  44. package/dist/solid.cjs.map +1 -1
  45. package/dist/solid.d.cts +2 -1
  46. package/dist/solid.d.ts +2 -1
  47. package/dist/solid.js +2 -1
  48. package/dist/solid.js.map +1 -1
  49. package/dist/svelte.cjs +4 -3
  50. package/dist/svelte.cjs.map +1 -1
  51. package/dist/svelte.d.cts +2 -1
  52. package/dist/svelte.d.ts +2 -1
  53. package/dist/svelte.js +2 -1
  54. package/dist/svelte.js.map +1 -1
  55. package/dist/themes/amber.css +316 -0
  56. package/dist/themes/aqua.css +285 -0
  57. package/dist/themes/blueprint.css +303 -0
  58. package/dist/themes/brutalist.css +307 -0
  59. package/dist/themes/candy.css +277 -0
  60. package/dist/themes/carbon.css +297 -0
  61. package/dist/themes/forest.css +278 -0
  62. package/dist/themes/frost.css +279 -0
  63. package/dist/themes/glass.css +2 -1
  64. package/dist/themes/light.css +2 -1
  65. package/dist/themes/neon.css +289 -0
  66. package/dist/themes/noir.css +282 -0
  67. package/dist/themes/paper.css +258 -0
  68. package/dist/themes/synth.css +283 -0
  69. package/dist/themes/terminal.css +273 -0
  70. package/dist/vue.cjs +4 -3
  71. package/dist/vue.cjs.map +1 -1
  72. package/dist/vue.d.cts +2 -1
  73. package/dist/vue.d.ts +2 -1
  74. package/dist/vue.js +2 -1
  75. package/dist/vue.js.map +1 -1
  76. package/package.json +37 -1
  77. package/dist/chunk-2VRCYMKH.cjs.map +0 -1
  78. package/dist/chunk-WOCAQEM6.js.map +0 -1
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  - ⌨️ **Accessible** — keyboard move/resize/snap, F6 window cycling, focus-trapped modals, `aria-live` announcements
25
25
  - ⚡ **Fast** — `transform`-only positioning, rAF-batched pointer input, structural sharing; 50 windows drag at 60fps
26
26
  - 💾 **Persistence** — one call to serialize the desktop, one call to restore it, with versioned migrations
27
- - 🎨 **Three themes** — dark glass, light glass and Win98 retro, or bring your own CSS
27
+ - 🎨 **Sixteen themes** — glass, light, retro, terminal, paper, neon, aqua, frost, candy, carbon, brutalist, blueprint, amber, noir, forest, synth — or bring your own CSS
28
28
  - 🖼️ **Popout** *(experimental)* — send a window into Document Picture-in-Picture
29
29
  - 📦 **Zero dependencies**, strict TypeScript, ESM + CJS, ~10.7 kB brotli core
30
30
 
@@ -326,6 +326,39 @@ persist(wm, {
326
326
 
327
327
  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()`.
328
328
 
329
+ ### `pinch(options?)`, `swipe(options?)`, `touchGestures(options?)` — `@surdeddd/wmkit/gestures`
330
+
331
+ Two-finger gestures, opt-in so the core never pays for them: pinch a window to resize it around the point between your fingers, swipe two fingers sideways to change workspace. 1.46 kB brotlied.
332
+
333
+ ```js
334
+ import { attachDesktop } from '@surdeddd/wmkit'
335
+ import { touchGestures } from '@surdeddd/wmkit/gestures'
336
+
337
+ attachDesktop(wm, root, { gestures: [touchGestures({ swipe: { workspaces: 4 } })] })
338
+ ```
339
+
340
+ A gesture is just a function that takes a `GestureContext` and returns its teardown, so your own gestures cooperate with the built-in drag and resize through the same `claim`/`busy` pair — see [docs/api.md](docs/api.md).
341
+
342
+ ### `createDevtools(wm, options?)` — `@surdeddd/wmkit/devtools`
343
+
344
+ An opt-in panel that shows the live window table, an event log and the manager controls, so you can see what the state machine is doing without a debugger. It follows the manager, never the DOM, so it works with any adapter or none.
345
+
346
+ ```js
347
+ import { createDevtools, devtoolsMessagesRu } from '@surdeddd/wmkit/devtools'
348
+
349
+ const panel = createDevtools(wm, { logLimit: 100 })
350
+ panel.destroy()
351
+ ```
352
+
353
+ | Option | Default | Meaning |
354
+ | --- | --- | --- |
355
+ | `container` | `document.body` | where the panel mounts |
356
+ | `messages` | English catalog | pass `devtoolsMessagesRu` for Russian, or your own strings |
357
+ | `logLimit` | `50` | ring buffer size for the event log |
358
+ | `events` | open, close, focus, stage, workspace, group, modalblocked | which manager events to log |
359
+
360
+ Rows are patched in place rather than re-rendered, so keyboard focus inside the panel survives while windows move. It injects one scoped stylesheet, shared between panels and removed with the last one.
361
+
329
362
  ## Theming
330
363
 
331
364
  `@surdeddd/wmkit/themes/glass.css` styles the `data-wm-*` attributes and exposes CSS variables:
@@ -339,7 +372,9 @@ Moves a window's content into a [Document Picture-in-Picture](https://developer.
339
372
  }
340
373
  ```
341
374
 
342
- 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.
375
+ Fifteen more ready-made themes ship alongside `light`, `retro`, `terminal`, `paper`, `neon`, `aqua`, `frost`, `candy`, `carbon`, `brutalist`, `blueprint`, `amber`, `noir`, `forest` and `synth`. They all style the same `data-wm-*` attributes, so switching is a one-line import swap, and every one of them ships 24px pointer targets plus `prefers-reduced-motion` and `forced-colors` blocks.
376
+
377
+ A theme dresses every window; a variant dresses one. Set `meta.variant` on a window and the desktop mirrors it to `data-wm-variant` for your CSS to pick up — see [docs/theming.md](docs/theming.md).
343
378
 
344
379
  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.
345
380
 
package/README.ru.md CHANGED
@@ -20,7 +20,7 @@
20
20
  - ⌨️ **Доступность** — move/resize/snap с клавиатуры, цикл окон по F6, focus-trap в модалках, `aria-live`-анонсы
21
21
  - ⚡ **Производительность** — позиционирование только через `transform`, rAF-батчинг ввода, structural sharing; 50 окон таскаются на 60fps
22
22
  - 💾 **Персист** — один вызов сериализует рабочий стол, один — восстанавливает, с версионированием и миграциями
23
- - 🎨 **Три темы**тёмное стекло, светлое стекло и Win98-ретро, либо полностью свой CSS
23
+ - 🎨 **Шестнадцать тем**glass, light, retro, terminal, paper, neon, aqua, frost, candy, carbon, brutalist, blueprint, amber, noir, forest, synth — либо полностью свой CSS
24
24
  - 🖼️ **Popout** *(experimental)* — вынос окна в Document Picture-in-Picture
25
25
  - 📦 **Ноль зависимостей**, строгий TypeScript, ESM + CJS, ~10.7 kB brotli
26
26
 
@@ -262,11 +262,46 @@ if (isPopoutSupported()) await popout(wm, 'docs', contentElement)
262
262
 
263
263
  Окно уезжает в настоящее always-on-top окно ОС (Document Picture-in-Picture) с тем же JS-контекстом и состоянием.
264
264
 
265
+ ### `pinch(options?)`, `swipe(options?)`, `touchGestures(options?)` — `@surdeddd/wmkit/gestures`
266
+
267
+ Жесты двумя пальцами, подключаются по желанию — ядро за них не платит: пинч растягивает окно вокруг точки между пальцами, свайп двумя пальцами вбок переключает рабочий стол. 1.46 кБ в brotli.
268
+
269
+ ```js
270
+ import { attachDesktop } from '@surdeddd/wmkit'
271
+ import { touchGestures } from '@surdeddd/wmkit/gestures'
272
+
273
+ attachDesktop(wm, root, { gestures: [touchGestures({ swipe: { workspaces: 4 } })] })
274
+ ```
275
+
276
+ Жест — это просто функция: принимает `GestureContext`, возвращает свою отписку. Свои жесты уживаются со встроенными драгом и ресайзом через ту же пару `claim`/`busy` — подробности в [docs/api.md](docs/api.md).
277
+
278
+ ### `createDevtools(wm, options?)` — `@surdeddd/wmkit/devtools`
279
+
280
+ Панель, которую подключают по желанию: живая таблица окон, журнал событий и управление менеджером — чтобы видеть, что делает машина состояний, без отладчика. Она следит за менеджером, а не за DOM, поэтому работает с любым адаптером и без него.
281
+
282
+ ```js
283
+ import { createDevtools, devtoolsMessagesRu } from '@surdeddd/wmkit/devtools'
284
+
285
+ const panel = createDevtools(wm, { messages: devtoolsMessagesRu })
286
+ panel.destroy()
287
+ ```
288
+
289
+ | Опция | По умолчанию | Значение |
290
+ | --- | --- | --- |
291
+ | `container` | `document.body` | куда монтируется панель |
292
+ | `messages` | английский каталог | `devtoolsMessagesRu` для русского или свои строки |
293
+ | `logLimit` | `50` | размер кольцевого буфера журнала |
294
+ | `events` | open, close, focus, stage, workspace, group, modalblocked | какие события менеджера писать |
295
+
296
+ Строки обновляются точечно, а не перерисовываются целиком, поэтому фокус клавиатуры внутри панели переживает перемещение окон. Панель добавляет один изолированный стиль — общий для всех панелей и удаляемый вместе с последней.
297
+
265
298
  ## Темизация
266
299
 
267
300
  Подключите `@surdeddd/wmkit/themes/glass.css` и переопределяйте CSS-переменные (`--wm-radius`, `--wm-bg`, `--wm-accent`, …) — или не подключайте ничего и стилизуйте `data-wm-stage`, `data-wm-focused`, `data-wm-dragging`, `[hidden]` сами.
268
301
 
269
- Ещё две готовые темы: `themes/light.css` (светлое стекло) и `themes/retro.css` (Win98-ностальгия). Все три стилизуют одни и те же `data-wm-*` атрибуты — переключение = замена одного импорта.
302
+ Ещё пятнадцать готовых тем: `light`, `retro`, `terminal`, `paper`, `neon`, `aqua`, `frost`, `candy`, `carbon`, `brutalist`, `blueprint`, `amber`, `noir`, `forest`, `synth`. Все стилизуют одни и те же `data-wm-*` атрибуты — переключение = замена одного импорта, и у каждой есть 24px тач-цели плюс блоки `prefers-reduced-motion` и `forced-colors`.
303
+
304
+ Тема одевает все окна, вариант — одно. Задайте `meta.variant` у окна, и десктоп отзеркалит его в `data-wm-variant` для вашего CSS — подробности в [docs/theming.md](docs/theming.md).
270
305
 
271
306
  ## SSR
272
307
 
package/dist/angular.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunk2VRCYMKH_cjs = require('./chunk-2VRCYMKH.cjs');
3
+ var chunkDINVOL6Z_cjs = require('./chunk-DINVOL6Z.cjs');
4
+ require('./chunk-BDHFXEJN.cjs');
4
5
  var core = require('@angular/core');
5
6
 
6
7
  function onDestroy(dispose) {
@@ -13,7 +14,7 @@ function onDestroy(dispose) {
13
14
  destroyRef?.onDestroy(dispose);
14
15
  }
15
16
  function useWindowManager(options) {
16
- const wm = chunk2VRCYMKH_cjs.createWindowManager(options);
17
+ const wm = chunkDINVOL6Z_cjs.createWindowManager(options);
17
18
  onDestroy(() => wm.destroy());
18
19
  return wm;
19
20
  }
@@ -28,7 +29,7 @@ function useWmWindow(wm, id) {
28
29
  return core.computed(() => state().windows[id]);
29
30
  }
30
31
  function createDesktop(wm, options) {
31
- const binder = chunk2VRCYMKH_cjs.createDesktopBinder(wm, options);
32
+ const binder = chunkDINVOL6Z_cjs.createDesktopBinder(wm, options);
32
33
  onDestroy(() => binder.destroy());
33
34
  return {
34
35
  binder,
@@ -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,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"]}
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"]}
@@ -1,6 +1,7 @@
1
1
  import { Signal } from '@angular/core';
2
2
  import { W as WindowManager, M as ManagerOptions, a as ManagerState, b as WindowState } from './types-jG2lHGcA.cjs';
3
- import { D as DesktopBinder, W as WindowAttachOptions, a as DesktopOptions } from './binder-_L3Iww0u.cjs';
3
+ import { D as DesktopBinder } from './binder-B625OrjD.cjs';
4
+ import { W as WindowAttachOptions, D as DesktopOptions } from './shared-DU_87nY9.cjs';
4
5
 
5
6
  declare function useWindowManager(options?: ManagerOptions): WindowManager;
6
7
  declare function useWmState(wm: WindowManager): Signal<ManagerState>;
package/dist/angular.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Signal } from '@angular/core';
2
2
  import { W as WindowManager, M as ManagerOptions, a as ManagerState, b as WindowState } from './types-jG2lHGcA.js';
3
- import { D as DesktopBinder, W as WindowAttachOptions, a as DesktopOptions } from './binder-DvQFmO3K.js';
3
+ import { D as DesktopBinder } from './binder-CpGY8Wye.js';
4
+ import { W as WindowAttachOptions, D as DesktopOptions } from './shared-BBXgEeIa.js';
4
5
 
5
6
  declare function useWindowManager(options?: ManagerOptions): WindowManager;
6
7
  declare function useWmState(wm: WindowManager): Signal<ManagerState>;
package/dist/angular.js CHANGED
@@ -1,4 +1,5 @@
1
- import { createWindowManager, createDesktopBinder } from './chunk-WOCAQEM6.js';
1
+ import { createWindowManager, createDesktopBinder } from './chunk-MGWCKHTZ.js';
2
+ import './chunk-5XVIC5LH.js';
2
3
  import { signal, computed, inject, DestroyRef } from '@angular/core';
3
4
 
4
5
  function onDestroy(dispose) {
@@ -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,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
+ {"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"]}
@@ -0,0 +1,13 @@
1
+ import { W as WindowManager } from './types-jG2lHGcA.cjs';
2
+ import { a as DesktopController, W as WindowAttachOptions, D as DesktopOptions } from './shared-DU_87nY9.cjs';
3
+
4
+ interface DesktopBinder {
5
+ wm: WindowManager;
6
+ controller(): DesktopController | null;
7
+ bindDesktop(element: HTMLElement): () => void;
8
+ bindWindow(id: string, element: HTMLElement, options?: WindowAttachOptions): () => void;
9
+ destroy(): void;
10
+ }
11
+ declare function createDesktopBinder(wm: WindowManager, options?: DesktopOptions): DesktopBinder;
12
+
13
+ export { type DesktopBinder as D, createDesktopBinder as c };
@@ -0,0 +1,13 @@
1
+ import { W as WindowManager } from './types-jG2lHGcA.js';
2
+ import { a as DesktopController, W as WindowAttachOptions, D as DesktopOptions } from './shared-BBXgEeIa.js';
3
+
4
+ interface DesktopBinder {
5
+ wm: WindowManager;
6
+ controller(): DesktopController | null;
7
+ bindDesktop(element: HTMLElement): () => void;
8
+ bindWindow(id: string, element: HTMLElement, options?: WindowAttachOptions): () => void;
9
+ destroy(): void;
10
+ }
11
+ declare function createDesktopBinder(wm: WindowManager, options?: DesktopOptions): DesktopBinder;
12
+
13
+ export { type DesktopBinder as D, createDesktopBinder as c };
@@ -0,0 +1,140 @@
1
+ // src/core/geometry.ts
2
+ function clamp(value, min, max) {
3
+ return Math.min(Math.max(value, min), max);
4
+ }
5
+ function clampSize(size, min, max) {
6
+ return {
7
+ width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY),
8
+ height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY)
9
+ };
10
+ }
11
+ function clampToViewport(bounds, viewport, minVisible) {
12
+ if (viewport.width <= 0 || viewport.height <= 0) return bounds;
13
+ const visibleX = Math.min(minVisible, bounds.width);
14
+ const visibleY = Math.min(minVisible, bounds.height);
15
+ const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX));
16
+ const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY));
17
+ return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y };
18
+ }
19
+ function boundsEqual(a, b) {
20
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
21
+ }
22
+ function applyAspect(size, ratio, min, max, drive) {
23
+ if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max);
24
+ const maxWidth = max ? max.width : Number.POSITIVE_INFINITY;
25
+ const maxHeight = max ? max.height : Number.POSITIVE_INFINITY;
26
+ if (drive === "height") {
27
+ const height = clamp(
28
+ size.height,
29
+ Math.max(min.height, min.width / ratio),
30
+ Math.min(maxHeight, maxWidth / ratio)
31
+ );
32
+ return { width: height * ratio, height };
33
+ }
34
+ const width = clamp(
35
+ size.width,
36
+ Math.max(min.width, min.height * ratio),
37
+ Math.min(maxWidth, maxHeight * ratio)
38
+ );
39
+ return { width, height: width / ratio };
40
+ }
41
+ function zoneBounds(zone, viewport) {
42
+ const w = viewport.width;
43
+ const h = viewport.height;
44
+ const halfW = Math.round(w / 2);
45
+ const halfH = Math.round(h / 2);
46
+ const oneThird = Math.round(w / 3);
47
+ const twoThirds = Math.round(w * 2 / 3);
48
+ switch (zone) {
49
+ case "left-third":
50
+ return { x: 0, y: 0, width: oneThird, height: h };
51
+ case "center-third":
52
+ return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h };
53
+ case "right-third":
54
+ return { x: twoThirds, y: 0, width: w - twoThirds, height: h };
55
+ case "left":
56
+ return { x: 0, y: 0, width: halfW, height: h };
57
+ case "right":
58
+ return { x: halfW, y: 0, width: w - halfW, height: h };
59
+ case "top":
60
+ return { x: 0, y: 0, width: w, height: halfH };
61
+ case "bottom":
62
+ return { x: 0, y: halfH, width: w, height: h - halfH };
63
+ case "top-left":
64
+ return { x: 0, y: 0, width: halfW, height: halfH };
65
+ case "top-right":
66
+ return { x: halfW, y: 0, width: w - halfW, height: halfH };
67
+ case "bottom-left":
68
+ return { x: 0, y: halfH, width: halfW, height: h - halfH };
69
+ case "bottom-right":
70
+ return { x: halfW, y: halfH, width: w - halfW, height: h - halfH };
71
+ }
72
+ }
73
+ function detectSnapZone(x, y, viewport, options = {}) {
74
+ const threshold = options.threshold ?? 12;
75
+ const cornerSize = options.cornerSize ?? 64;
76
+ const w = viewport.width;
77
+ const h = viewport.height;
78
+ if (w <= 0 || h <= 0) return null;
79
+ const nearLeft = x <= threshold;
80
+ const nearRight = x >= w - threshold;
81
+ const nearTop = y <= threshold;
82
+ const nearBottom = y >= h - threshold;
83
+ if (nearLeft || nearRight) {
84
+ const side = nearLeft ? "left" : "right";
85
+ if (y <= cornerSize) return `top-${side}`;
86
+ if (y >= h - cornerSize) return `bottom-${side}`;
87
+ return side;
88
+ }
89
+ if (nearTop) {
90
+ if (x <= cornerSize) return "top-left";
91
+ if (x >= w - cornerSize) return "top-right";
92
+ return "top";
93
+ }
94
+ if (nearBottom) {
95
+ if (x <= cornerSize) return "bottom-left";
96
+ if (x >= w - cornerSize) return "bottom-right";
97
+ return "bottom";
98
+ }
99
+ return null;
100
+ }
101
+ function nearestEdge(low, high, targetLow, targetHigh) {
102
+ return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high];
103
+ }
104
+ function magnetize(bounds, targets, threshold) {
105
+ const result = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false };
106
+ if (threshold <= 0 || targets.length === 0) return result;
107
+ let bestX = threshold + 1;
108
+ let bestY = threshold + 1;
109
+ for (const target of targets) {
110
+ for (const delta of nearestEdge(
111
+ bounds.x,
112
+ bounds.x + bounds.width,
113
+ target.x,
114
+ target.x + target.width
115
+ )) {
116
+ if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta;
117
+ }
118
+ for (const delta of nearestEdge(
119
+ bounds.y,
120
+ bounds.y + bounds.height,
121
+ target.y,
122
+ target.y + target.height
123
+ )) {
124
+ if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta;
125
+ }
126
+ }
127
+ if (Math.abs(bestX) <= threshold) {
128
+ result.x = bounds.x + bestX;
129
+ result.snappedX = true;
130
+ }
131
+ if (Math.abs(bestY) <= threshold) {
132
+ result.y = bounds.y + bestY;
133
+ result.snappedY = true;
134
+ }
135
+ return result;
136
+ }
137
+
138
+ export { applyAspect, boundsEqual, clamp, clampSize, clampToViewport, detectSnapZone, magnetize, zoneBounds };
139
+ //# sourceMappingURL=chunk-5XVIC5LH.js.map
140
+ //# sourceMappingURL=chunk-5XVIC5LH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/geometry.ts"],"names":[],"mappings":"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","file":"chunk-5XVIC5LH.js","sourcesContent":["import type { Bounds, Size, SnapZone } from './types'\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nexport function clampSize(size: Size, min: Size, max: Size | null): Size {\n return {\n width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY),\n height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY),\n }\n}\n\nexport function clampToViewport(bounds: Bounds, viewport: Size, minVisible: number): Bounds {\n if (viewport.width <= 0 || viewport.height <= 0) return bounds\n const visibleX = Math.min(minVisible, bounds.width)\n const visibleY = Math.min(minVisible, bounds.height)\n const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX))\n const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY))\n return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y }\n}\n\nexport function boundsEqual(a: Bounds, b: Bounds): boolean {\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n}\n\nexport function applyAspect(\n size: Size,\n ratio: number,\n min: Size,\n max: Size | null,\n drive: 'width' | 'height',\n): Size {\n if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max)\n const maxWidth = max ? max.width : Number.POSITIVE_INFINITY\n const maxHeight = max ? max.height : Number.POSITIVE_INFINITY\n if (drive === 'height') {\n const height = clamp(\n size.height,\n Math.max(min.height, min.width / ratio),\n Math.min(maxHeight, maxWidth / ratio),\n )\n return { width: height * ratio, height }\n }\n const width = clamp(\n size.width,\n Math.max(min.width, min.height * ratio),\n Math.min(maxWidth, maxHeight * ratio),\n )\n return { width, height: width / ratio }\n}\n\nexport function zoneBounds(zone: SnapZone, viewport: Size): Bounds {\n const w = viewport.width\n const h = viewport.height\n const halfW = Math.round(w / 2)\n const halfH = Math.round(h / 2)\n const oneThird = Math.round(w / 3)\n const twoThirds = Math.round((w * 2) / 3)\n switch (zone) {\n case 'left-third':\n return { x: 0, y: 0, width: oneThird, height: h }\n case 'center-third':\n return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h }\n case 'right-third':\n return { x: twoThirds, y: 0, width: w - twoThirds, height: h }\n case 'left':\n return { x: 0, y: 0, width: halfW, height: h }\n case 'right':\n return { x: halfW, y: 0, width: w - halfW, height: h }\n case 'top':\n return { x: 0, y: 0, width: w, height: halfH }\n case 'bottom':\n return { x: 0, y: halfH, width: w, height: h - halfH }\n case 'top-left':\n return { x: 0, y: 0, width: halfW, height: halfH }\n case 'top-right':\n return { x: halfW, y: 0, width: w - halfW, height: halfH }\n case 'bottom-left':\n return { x: 0, y: halfH, width: halfW, height: h - halfH }\n case 'bottom-right':\n return { x: halfW, y: halfH, width: w - halfW, height: h - halfH }\n }\n}\n\nexport interface SnapDetectOptions {\n threshold?: number\n cornerSize?: number\n}\n\nexport function detectSnapZone(\n x: number,\n y: number,\n viewport: Size,\n options: SnapDetectOptions = {},\n): SnapZone | null {\n const threshold = options.threshold ?? 12\n const cornerSize = options.cornerSize ?? 64\n const w = viewport.width\n const h = viewport.height\n if (w <= 0 || h <= 0) return null\n\n const nearLeft = x <= threshold\n const nearRight = x >= w - threshold\n const nearTop = y <= threshold\n const nearBottom = y >= h - threshold\n\n if (nearLeft || nearRight) {\n const side = nearLeft ? 'left' : 'right'\n if (y <= cornerSize) return `top-${side}` as SnapZone\n if (y >= h - cornerSize) return `bottom-${side}` as SnapZone\n return side\n }\n if (nearTop) {\n if (x <= cornerSize) return 'top-left'\n if (x >= w - cornerSize) return 'top-right'\n return 'top'\n }\n if (nearBottom) {\n if (x <= cornerSize) return 'bottom-left'\n if (x >= w - cornerSize) return 'bottom-right'\n return 'bottom'\n }\n return null\n}\n\nexport interface MagnetResult {\n x: number\n y: number\n snappedX: boolean\n snappedY: boolean\n}\n\nfunction nearestEdge(low: number, high: number, targetLow: number, targetHigh: number): number[] {\n return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high]\n}\n\nexport function magnetize(\n bounds: Bounds,\n targets: readonly Bounds[],\n threshold: number,\n): MagnetResult {\n const result: MagnetResult = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false }\n if (threshold <= 0 || targets.length === 0) return result\n let bestX = threshold + 1\n let bestY = threshold + 1\n for (const target of targets) {\n for (const delta of nearestEdge(\n bounds.x,\n bounds.x + bounds.width,\n target.x,\n target.x + target.width,\n )) {\n if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta\n }\n for (const delta of nearestEdge(\n bounds.y,\n bounds.y + bounds.height,\n target.y,\n target.y + target.height,\n )) {\n if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta\n }\n }\n if (Math.abs(bestX) <= threshold) {\n result.x = bounds.x + bestX\n result.snappedX = true\n }\n if (Math.abs(bestY) <= threshold) {\n result.y = bounds.y + bestY\n result.snappedY = true\n }\n return result\n}\n"]}
@@ -0,0 +1,149 @@
1
+ 'use strict';
2
+
3
+ // src/core/geometry.ts
4
+ function clamp(value, min, max) {
5
+ return Math.min(Math.max(value, min), max);
6
+ }
7
+ function clampSize(size, min, max) {
8
+ return {
9
+ width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY),
10
+ height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY)
11
+ };
12
+ }
13
+ function clampToViewport(bounds, viewport, minVisible) {
14
+ if (viewport.width <= 0 || viewport.height <= 0) return bounds;
15
+ const visibleX = Math.min(minVisible, bounds.width);
16
+ const visibleY = Math.min(minVisible, bounds.height);
17
+ const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX));
18
+ const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY));
19
+ return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y };
20
+ }
21
+ function boundsEqual(a, b) {
22
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
23
+ }
24
+ function applyAspect(size, ratio, min, max, drive) {
25
+ if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max);
26
+ const maxWidth = max ? max.width : Number.POSITIVE_INFINITY;
27
+ const maxHeight = max ? max.height : Number.POSITIVE_INFINITY;
28
+ if (drive === "height") {
29
+ const height = clamp(
30
+ size.height,
31
+ Math.max(min.height, min.width / ratio),
32
+ Math.min(maxHeight, maxWidth / ratio)
33
+ );
34
+ return { width: height * ratio, height };
35
+ }
36
+ const width = clamp(
37
+ size.width,
38
+ Math.max(min.width, min.height * ratio),
39
+ Math.min(maxWidth, maxHeight * ratio)
40
+ );
41
+ return { width, height: width / ratio };
42
+ }
43
+ function zoneBounds(zone, viewport) {
44
+ const w = viewport.width;
45
+ const h = viewport.height;
46
+ const halfW = Math.round(w / 2);
47
+ const halfH = Math.round(h / 2);
48
+ const oneThird = Math.round(w / 3);
49
+ const twoThirds = Math.round(w * 2 / 3);
50
+ switch (zone) {
51
+ case "left-third":
52
+ return { x: 0, y: 0, width: oneThird, height: h };
53
+ case "center-third":
54
+ return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h };
55
+ case "right-third":
56
+ return { x: twoThirds, y: 0, width: w - twoThirds, height: h };
57
+ case "left":
58
+ return { x: 0, y: 0, width: halfW, height: h };
59
+ case "right":
60
+ return { x: halfW, y: 0, width: w - halfW, height: h };
61
+ case "top":
62
+ return { x: 0, y: 0, width: w, height: halfH };
63
+ case "bottom":
64
+ return { x: 0, y: halfH, width: w, height: h - halfH };
65
+ case "top-left":
66
+ return { x: 0, y: 0, width: halfW, height: halfH };
67
+ case "top-right":
68
+ return { x: halfW, y: 0, width: w - halfW, height: halfH };
69
+ case "bottom-left":
70
+ return { x: 0, y: halfH, width: halfW, height: h - halfH };
71
+ case "bottom-right":
72
+ return { x: halfW, y: halfH, width: w - halfW, height: h - halfH };
73
+ }
74
+ }
75
+ function detectSnapZone(x, y, viewport, options = {}) {
76
+ const threshold = options.threshold ?? 12;
77
+ const cornerSize = options.cornerSize ?? 64;
78
+ const w = viewport.width;
79
+ const h = viewport.height;
80
+ if (w <= 0 || h <= 0) return null;
81
+ const nearLeft = x <= threshold;
82
+ const nearRight = x >= w - threshold;
83
+ const nearTop = y <= threshold;
84
+ const nearBottom = y >= h - threshold;
85
+ if (nearLeft || nearRight) {
86
+ const side = nearLeft ? "left" : "right";
87
+ if (y <= cornerSize) return `top-${side}`;
88
+ if (y >= h - cornerSize) return `bottom-${side}`;
89
+ return side;
90
+ }
91
+ if (nearTop) {
92
+ if (x <= cornerSize) return "top-left";
93
+ if (x >= w - cornerSize) return "top-right";
94
+ return "top";
95
+ }
96
+ if (nearBottom) {
97
+ if (x <= cornerSize) return "bottom-left";
98
+ if (x >= w - cornerSize) return "bottom-right";
99
+ return "bottom";
100
+ }
101
+ return null;
102
+ }
103
+ function nearestEdge(low, high, targetLow, targetHigh) {
104
+ return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high];
105
+ }
106
+ function magnetize(bounds, targets, threshold) {
107
+ const result = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false };
108
+ if (threshold <= 0 || targets.length === 0) return result;
109
+ let bestX = threshold + 1;
110
+ let bestY = threshold + 1;
111
+ for (const target of targets) {
112
+ for (const delta of nearestEdge(
113
+ bounds.x,
114
+ bounds.x + bounds.width,
115
+ target.x,
116
+ target.x + target.width
117
+ )) {
118
+ if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta;
119
+ }
120
+ for (const delta of nearestEdge(
121
+ bounds.y,
122
+ bounds.y + bounds.height,
123
+ target.y,
124
+ target.y + target.height
125
+ )) {
126
+ if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta;
127
+ }
128
+ }
129
+ if (Math.abs(bestX) <= threshold) {
130
+ result.x = bounds.x + bestX;
131
+ result.snappedX = true;
132
+ }
133
+ if (Math.abs(bestY) <= threshold) {
134
+ result.y = bounds.y + bestY;
135
+ result.snappedY = true;
136
+ }
137
+ return result;
138
+ }
139
+
140
+ exports.applyAspect = applyAspect;
141
+ exports.boundsEqual = boundsEqual;
142
+ exports.clamp = clamp;
143
+ exports.clampSize = clampSize;
144
+ exports.clampToViewport = clampToViewport;
145
+ exports.detectSnapZone = detectSnapZone;
146
+ exports.magnetize = magnetize;
147
+ exports.zoneBounds = zoneBounds;
148
+ //# sourceMappingURL=chunk-BDHFXEJN.cjs.map
149
+ //# sourceMappingURL=chunk-BDHFXEJN.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/geometry.ts"],"names":[],"mappings":"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","file":"chunk-BDHFXEJN.cjs","sourcesContent":["import type { Bounds, Size, SnapZone } from './types'\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nexport function clampSize(size: Size, min: Size, max: Size | null): Size {\n return {\n width: clamp(size.width, min.width, max ? max.width : Number.POSITIVE_INFINITY),\n height: clamp(size.height, min.height, max ? max.height : Number.POSITIVE_INFINITY),\n }\n}\n\nexport function clampToViewport(bounds: Bounds, viewport: Size, minVisible: number): Bounds {\n if (viewport.width <= 0 || viewport.height <= 0) return bounds\n const visibleX = Math.min(minVisible, bounds.width)\n const visibleY = Math.min(minVisible, bounds.height)\n const x = clamp(bounds.x, visibleX - bounds.width, Math.max(0, viewport.width - visibleX))\n const y = clamp(bounds.y, 0, Math.max(0, viewport.height - visibleY))\n return x === bounds.x && y === bounds.y ? bounds : { ...bounds, x, y }\n}\n\nexport function boundsEqual(a: Bounds, b: Bounds): boolean {\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n}\n\nexport function applyAspect(\n size: Size,\n ratio: number,\n min: Size,\n max: Size | null,\n drive: 'width' | 'height',\n): Size {\n if (!Number.isFinite(ratio) || ratio <= 0) return clampSize(size, min, max)\n const maxWidth = max ? max.width : Number.POSITIVE_INFINITY\n const maxHeight = max ? max.height : Number.POSITIVE_INFINITY\n if (drive === 'height') {\n const height = clamp(\n size.height,\n Math.max(min.height, min.width / ratio),\n Math.min(maxHeight, maxWidth / ratio),\n )\n return { width: height * ratio, height }\n }\n const width = clamp(\n size.width,\n Math.max(min.width, min.height * ratio),\n Math.min(maxWidth, maxHeight * ratio),\n )\n return { width, height: width / ratio }\n}\n\nexport function zoneBounds(zone: SnapZone, viewport: Size): Bounds {\n const w = viewport.width\n const h = viewport.height\n const halfW = Math.round(w / 2)\n const halfH = Math.round(h / 2)\n const oneThird = Math.round(w / 3)\n const twoThirds = Math.round((w * 2) / 3)\n switch (zone) {\n case 'left-third':\n return { x: 0, y: 0, width: oneThird, height: h }\n case 'center-third':\n return { x: oneThird, y: 0, width: twoThirds - oneThird, height: h }\n case 'right-third':\n return { x: twoThirds, y: 0, width: w - twoThirds, height: h }\n case 'left':\n return { x: 0, y: 0, width: halfW, height: h }\n case 'right':\n return { x: halfW, y: 0, width: w - halfW, height: h }\n case 'top':\n return { x: 0, y: 0, width: w, height: halfH }\n case 'bottom':\n return { x: 0, y: halfH, width: w, height: h - halfH }\n case 'top-left':\n return { x: 0, y: 0, width: halfW, height: halfH }\n case 'top-right':\n return { x: halfW, y: 0, width: w - halfW, height: halfH }\n case 'bottom-left':\n return { x: 0, y: halfH, width: halfW, height: h - halfH }\n case 'bottom-right':\n return { x: halfW, y: halfH, width: w - halfW, height: h - halfH }\n }\n}\n\nexport interface SnapDetectOptions {\n threshold?: number\n cornerSize?: number\n}\n\nexport function detectSnapZone(\n x: number,\n y: number,\n viewport: Size,\n options: SnapDetectOptions = {},\n): SnapZone | null {\n const threshold = options.threshold ?? 12\n const cornerSize = options.cornerSize ?? 64\n const w = viewport.width\n const h = viewport.height\n if (w <= 0 || h <= 0) return null\n\n const nearLeft = x <= threshold\n const nearRight = x >= w - threshold\n const nearTop = y <= threshold\n const nearBottom = y >= h - threshold\n\n if (nearLeft || nearRight) {\n const side = nearLeft ? 'left' : 'right'\n if (y <= cornerSize) return `top-${side}` as SnapZone\n if (y >= h - cornerSize) return `bottom-${side}` as SnapZone\n return side\n }\n if (nearTop) {\n if (x <= cornerSize) return 'top-left'\n if (x >= w - cornerSize) return 'top-right'\n return 'top'\n }\n if (nearBottom) {\n if (x <= cornerSize) return 'bottom-left'\n if (x >= w - cornerSize) return 'bottom-right'\n return 'bottom'\n }\n return null\n}\n\nexport interface MagnetResult {\n x: number\n y: number\n snappedX: boolean\n snappedY: boolean\n}\n\nfunction nearestEdge(low: number, high: number, targetLow: number, targetHigh: number): number[] {\n return [targetLow - low, targetHigh - low, targetLow - high, targetHigh - high]\n}\n\nexport function magnetize(\n bounds: Bounds,\n targets: readonly Bounds[],\n threshold: number,\n): MagnetResult {\n const result: MagnetResult = { x: bounds.x, y: bounds.y, snappedX: false, snappedY: false }\n if (threshold <= 0 || targets.length === 0) return result\n let bestX = threshold + 1\n let bestY = threshold + 1\n for (const target of targets) {\n for (const delta of nearestEdge(\n bounds.x,\n bounds.x + bounds.width,\n target.x,\n target.x + target.width,\n )) {\n if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestX)) bestX = delta\n }\n for (const delta of nearestEdge(\n bounds.y,\n bounds.y + bounds.height,\n target.y,\n target.y + target.height,\n )) {\n if (Math.abs(delta) <= threshold && Math.abs(delta) < Math.abs(bestY)) bestY = delta\n }\n }\n if (Math.abs(bestX) <= threshold) {\n result.x = bounds.x + bestX\n result.snappedX = true\n }\n if (Math.abs(bestY) <= threshold) {\n result.y = bounds.y + bestY\n result.snappedY = true\n }\n return result\n}\n"]}