@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.
- package/README.md +37 -2
- package/README.ru.md +37 -2
- package/dist/angular.cjs +4 -3
- package/dist/angular.cjs.map +1 -1
- package/dist/angular.d.cts +2 -1
- package/dist/angular.d.ts +2 -1
- package/dist/angular.js +2 -1
- package/dist/angular.js.map +1 -1
- package/dist/binder-B625OrjD.d.cts +13 -0
- package/dist/binder-CpGY8Wye.d.ts +13 -0
- package/dist/chunk-5XVIC5LH.js +140 -0
- package/dist/chunk-5XVIC5LH.js.map +1 -0
- package/dist/chunk-BDHFXEJN.cjs +149 -0
- package/dist/chunk-BDHFXEJN.cjs.map +1 -0
- package/dist/{chunk-2VRCYMKH.cjs → chunk-DINVOL6Z.cjs} +63 -178
- package/dist/chunk-DINVOL6Z.cjs.map +1 -0
- package/dist/{chunk-WOCAQEM6.js → chunk-MGWCKHTZ.js} +44 -151
- package/dist/chunk-MGWCKHTZ.js.map +1 -0
- package/dist/devtools.cjs +330 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +43 -0
- package/dist/devtools.d.ts +43 -0
- package/dist/devtools.js +326 -0
- package/dist/devtools.js.map +1 -0
- package/dist/gestures.cjs +219 -0
- package/dist/gestures.cjs.map +1 -0
- package/dist/gestures.d.cts +19 -0
- package/dist/gestures.d.ts +19 -0
- package/dist/gestures.js +215 -0
- package/dist/gestures.js.map +1 -0
- package/dist/index.cjs +35 -34
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/react.cjs +4 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -1
- package/dist/react.d.ts +2 -1
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/dist/{binder-DvQFmO3K.d.ts → shared-BBXgEeIa.d.ts} +22 -8
- package/dist/{binder-_L3Iww0u.d.cts → shared-DU_87nY9.d.cts} +22 -8
- package/dist/solid.cjs +4 -3
- package/dist/solid.cjs.map +1 -1
- package/dist/solid.d.cts +2 -1
- package/dist/solid.d.ts +2 -1
- package/dist/solid.js +2 -1
- package/dist/solid.js.map +1 -1
- package/dist/svelte.cjs +4 -3
- package/dist/svelte.cjs.map +1 -1
- package/dist/svelte.d.cts +2 -1
- package/dist/svelte.d.ts +2 -1
- package/dist/svelte.js +2 -1
- package/dist/svelte.js.map +1 -1
- package/dist/themes/amber.css +316 -0
- package/dist/themes/aqua.css +285 -0
- package/dist/themes/blueprint.css +303 -0
- package/dist/themes/brutalist.css +307 -0
- package/dist/themes/candy.css +277 -0
- package/dist/themes/carbon.css +297 -0
- package/dist/themes/forest.css +278 -0
- package/dist/themes/frost.css +279 -0
- package/dist/themes/glass.css +2 -1
- package/dist/themes/light.css +2 -1
- package/dist/themes/neon.css +289 -0
- package/dist/themes/noir.css +282 -0
- package/dist/themes/paper.css +258 -0
- package/dist/themes/synth.css +283 -0
- package/dist/themes/terminal.css +273 -0
- package/dist/vue.cjs +4 -3
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.d.cts +2 -1
- package/dist/vue.d.ts +2 -1
- package/dist/vue.js +2 -1
- package/dist/vue.js.map +1 -1
- package/package.json +37 -1
- package/dist/chunk-2VRCYMKH.cjs.map +0 -1
- 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
|
-
- 🎨 **
|
|
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
|
-
|
|
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
|
-
- 🎨
|
|
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
|
-
Ещё
|
|
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
|
|
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 =
|
|
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 =
|
|
32
|
+
const binder = chunkDINVOL6Z_cjs.createDesktopBinder(wm, options);
|
|
32
33
|
onDestroy(() => binder.destroy());
|
|
33
34
|
return {
|
|
34
35
|
binder,
|
package/dist/angular.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/angular.ts"],"names":["inject","DestroyRef","createWindowManager","signal","computed","createDesktopBinder"],"mappings":"
|
|
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,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
|
|
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
|
|
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-
|
|
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) {
|
package/dist/angular.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/angular.ts"],"names":[],"mappings":"
|
|
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"]}
|