@surdeddd/wmkit 0.4.0 → 0.4.2
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 +14 -2
- package/README.ru.md +136 -3
- package/dist/angular.cjs +3 -3
- package/dist/angular.js +1 -1
- package/dist/{chunk-FPZDAKNP.js → chunk-GGDF2EWV.js} +61 -16
- package/dist/chunk-GGDF2EWV.js.map +1 -0
- package/dist/{chunk-3KZZOLHW.cjs → chunk-GLBVT4AQ.cjs} +61 -16
- package/dist/chunk-GLBVT4AQ.cjs.map +1 -0
- package/dist/index.cjs +18 -18
- package/dist/index.js +1 -1
- package/dist/react.cjs +3 -3
- package/dist/react.js +1 -1
- package/dist/solid.cjs +3 -3
- package/dist/solid.js +1 -1
- package/dist/svelte.cjs +3 -3
- package/dist/svelte.js +1 -1
- package/dist/vue.cjs +3 -3
- package/dist/vue.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3KZZOLHW.cjs.map +0 -1
- package/dist/chunk-FPZDAKNP.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Headless window manager for the web.** Draggable, resizable, snappable windows with a taskbar model, keyboard accessibility and state persistence — for vanilla JS and every major framework.
|
|
4
4
|
|
|
5
|
-
[Русская версия](./README.ru.md) · [Live demo](https://wmkit.vercel.app) · [Mirror (Pages)](https://surdeddd.github.io/wmkit/) · [GitHub](https://github.com/Surdeddd/wmkit)
|
|
5
|
+
[Русская версия](./README.ru.md) · [Live demo](https://wmkit.vercel.app) · [Mirror (Pages)](https://surdeddd.github.io/wmkit/) · [Docs](./docs/README.md) · [GitHub](https://github.com/Surdeddd/wmkit)
|
|
6
6
|
|
|
7
7
|
[](https://github.com/Surdeddd/wmkit/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/@surdeddd/wmkit)
|
|
@@ -346,6 +346,18 @@ Skip the import entirely and the library stays headless: state attributes (`data
|
|
|
346
346
|
|
|
347
347
|
The core never touches `window`/`document` — create managers and even `hydrate()` state on the server, then call `attachDesktop` after mount. `persist` no-ops without usable storage.
|
|
348
348
|
|
|
349
|
+
## Documentation
|
|
350
|
+
|
|
351
|
+
This README is the tour. The reference lives in [`docs/`](./docs/README.md):
|
|
352
|
+
|
|
353
|
+
| Page | What is in it |
|
|
354
|
+
| --- | --- |
|
|
355
|
+
| [API reference](./docs/api.md) | every export, option, method, event and type |
|
|
356
|
+
| [Adapters](./docs/adapters.md) | complete React, Vue, Svelte, Solid and Angular integrations |
|
|
357
|
+
| [Theming](./docs/theming.md) | `data-wm-*` contract, CSS variables, writing a theme from scratch |
|
|
358
|
+
| [Recipes](./docs/recipes.md) | taskbar, modals, persistence, workspaces, SSR, testing, performance |
|
|
359
|
+
| [Browser support](./docs/browser-support.md) | baselines per entry point and what degrades where |
|
|
360
|
+
|
|
349
361
|
## Comparison
|
|
350
362
|
|
|
351
363
|
| | wmkit | WinBox | jsPanel4 | Dockview | Zag floating-panel |
|
|
@@ -364,7 +376,7 @@ The core never touches `window`/`document` — create managers and even `hydrate
|
|
|
364
376
|
|
|
365
377
|
## Quality
|
|
366
378
|
|
|
367
|
-
-
|
|
379
|
+
- 250 unit tests: **100%** line/branch/function/statement coverage on the core state machine and persistence, with enforced floors on the DOM layer and the adapters
|
|
368
380
|
- 190+ Playwright scenarios on Chromium, WebKit and mobile emulation: drag, 8-way resize, snap, magnetism, workspaces, undo after drag, keyboard, touch, persistence across reloads, 50-window stress, modal traps, axe accessibility scans, visual regression screenshots
|
|
369
381
|
- performance benchmarks run in CI on every push (`vitest bench`): 1 000 windows open in ~150 ms, a move among 50 windows costs ~1.2 µs, a full 100-step undo/redo sweep ~52 µs
|
|
370
382
|
- `publint` + `@arethetypeswrong/cli` validate the published package, `size-limit` guards bundle budgets
|
package/README.ru.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Headless оконный менеджер для веба.** Перетаскиваемые окна с ресайзом, снэпом, таскбаром, клавиатурной доступностью и персистом состояния — для vanilla JS и всех основных фреймворков.
|
|
4
4
|
|
|
5
|
-
[English version](./README.md) · [Живое демо](https://wmkit.vercel.app) · [Зеркало (Pages)](https://surdeddd.github.io/wmkit/) · [GitHub](https://github.com/Surdeddd/wmkit)
|
|
5
|
+
[English version](./README.md) · [Живое демо](https://wmkit.vercel.app) · [Зеркало (Pages)](https://surdeddd.github.io/wmkit/) · [Документация](./docs/README.md) · [GitHub](https://github.com/Surdeddd/wmkit)
|
|
6
6
|
|
|
7
7
|
[](https://surdeddd.github.io/wmkit/)
|
|
8
8
|
|
|
@@ -73,7 +73,140 @@ desktop.attachWindow(win.id, el, { removeOnClose: true })
|
|
|
73
73
|
|
|
74
74
|
## Адаптеры
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Каждый адаптер — тонкая обёртка над тем же ядром и тем же DOM-контроллером, около 60 строк. Они нужны только чтобы привязаться и отвязаться в нужный момент жизненного цикла компонента. Контент окна всегда живёт в дереве вашего фреймворка: никакого innerHTML, порталов и переноса узлов.
|
|
77
|
+
|
|
78
|
+
### React
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { useWindowManager, useDesktop, useWmState, useWmWindowRef } from '@surdeddd/wmkit/react'
|
|
82
|
+
|
|
83
|
+
function Desktop() {
|
|
84
|
+
const wm = useWindowManager()
|
|
85
|
+
const { ref, binder } = useDesktop(wm)
|
|
86
|
+
const state = useWmState(wm)
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div ref={ref} style={{ position: 'relative', height: '100vh' }}>
|
|
90
|
+
<button type="button" onClick={() => wm.open({ title: 'Окно' })}>открыть</button>
|
|
91
|
+
{state.order.map((id) => (
|
|
92
|
+
<Win key={id} binder={binder} win={state.windows[id]} />
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Win({ binder, win }) {
|
|
99
|
+
const ref = useWmWindowRef(binder, win.id, { removeOnClose: true })
|
|
100
|
+
return (
|
|
101
|
+
<section ref={ref}>
|
|
102
|
+
<header data-wm-drag><span data-wm-title>{win.title}</span></header>
|
|
103
|
+
<div data-wm-content>ваше дерево компонентов</div>
|
|
104
|
+
</section>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Vue
|
|
110
|
+
|
|
111
|
+
```vue
|
|
112
|
+
<script setup lang="ts">
|
|
113
|
+
import { ref } from 'vue'
|
|
114
|
+
import { useWindowManager, useDesktop, useWmWindowEl } from '@surdeddd/wmkit/vue'
|
|
115
|
+
|
|
116
|
+
const host = ref<HTMLElement>()
|
|
117
|
+
const panel = ref<HTMLElement>()
|
|
118
|
+
const wm = useWindowManager()
|
|
119
|
+
const binder = useDesktop(wm, host)
|
|
120
|
+
useWmWindowEl(binder, 'notes', panel)
|
|
121
|
+
wm.open({ id: 'notes', title: 'Заметки' })
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<template>
|
|
125
|
+
<div ref="host" style="position: relative; height: 100vh">
|
|
126
|
+
<section ref="panel">
|
|
127
|
+
<header data-wm-drag><span data-wm-title>Заметки</span></header>
|
|
128
|
+
<div data-wm-content>композаблы</div>
|
|
129
|
+
</section>
|
|
130
|
+
</div>
|
|
131
|
+
</template>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Svelte
|
|
135
|
+
|
|
136
|
+
```svelte
|
|
137
|
+
<script lang="ts">
|
|
138
|
+
import { createManager, createDesktop, wmWindowStore } from '@surdeddd/wmkit/svelte'
|
|
139
|
+
|
|
140
|
+
const wm = createManager()
|
|
141
|
+
const dk = createDesktop(wm)
|
|
142
|
+
wm.open({ id: 'notes', title: 'Заметки' })
|
|
143
|
+
const notes = wmWindowStore(wm, 'notes')
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<div use:dk.desktop style="position: relative; height: 100vh">
|
|
147
|
+
<section use:dk.window={{ id: 'notes' }}>
|
|
148
|
+
<header data-wm-drag><span data-wm-title>{$notes?.title}</span></header>
|
|
149
|
+
<div data-wm-content>сторы и экшены</div>
|
|
150
|
+
</section>
|
|
151
|
+
</div>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Solid
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
import { useWindowManager, createDesktop, useWmState } from '@surdeddd/wmkit/solid'
|
|
158
|
+
|
|
159
|
+
function Desktop() {
|
|
160
|
+
const wm = useWindowManager()
|
|
161
|
+
const dk = createDesktop(wm)
|
|
162
|
+
const state = useWmState(wm)
|
|
163
|
+
wm.open({ id: 'notes', title: 'Заметки' })
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div ref={dk.desktop} style={{ position: 'relative', height: '100vh' }}>
|
|
167
|
+
<section ref={dk.window('notes')}>
|
|
168
|
+
<header data-wm-drag><span data-wm-title>Заметки</span></header>
|
|
169
|
+
<div data-wm-content>{state().order.length} окон</div>
|
|
170
|
+
</section>
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Angular
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { Component, ElementRef, afterNextRender, viewChild } from '@angular/core'
|
|
180
|
+
import { useWindowManager, createDesktop, useWmState } from '@surdeddd/wmkit/angular'
|
|
181
|
+
|
|
182
|
+
@Component({
|
|
183
|
+
selector: 'app-desktop',
|
|
184
|
+
template: `
|
|
185
|
+
<div #host style="position: relative; height: 100vh">
|
|
186
|
+
<section #panel>
|
|
187
|
+
<header data-wm-drag><span data-wm-title>Заметки</span></header>
|
|
188
|
+
<div data-wm-content>{{ state().order.length }} окон</div>
|
|
189
|
+
</section>
|
|
190
|
+
</div>`,
|
|
191
|
+
})
|
|
192
|
+
export class DesktopComponent {
|
|
193
|
+
private host = viewChild.required<ElementRef>('host')
|
|
194
|
+
private panel = viewChild.required<ElementRef>('panel')
|
|
195
|
+
wm = useWindowManager()
|
|
196
|
+
state = useWmState(this.wm)
|
|
197
|
+
|
|
198
|
+
constructor() {
|
|
199
|
+
const dk = createDesktop(this.wm)
|
|
200
|
+
afterNextRender(() => {
|
|
201
|
+
dk.desktop(this.host().nativeElement)
|
|
202
|
+
this.wm.open({ id: 'notes', title: 'Заметки' })
|
|
203
|
+
dk.window('notes')(this.panel().nativeElement)
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Полные примеры с таскбаром, модалками и сигнатурами всех хуков — в [docs/adapters.md](./docs/adapters.md).
|
|
77
210
|
|
|
78
211
|
## API ядра — кратко
|
|
79
212
|
|
|
@@ -140,7 +273,7 @@ if (isPopoutSupported()) await popout(wm, 'docs', contentElement)
|
|
|
140
273
|
|
|
141
274
|
## Качество
|
|
142
275
|
|
|
143
|
-
-
|
|
276
|
+
- 250 юнит-тестов: **100%** покрытие стейт-машины и persist по строкам/веткам/функциям, плюс обязательные пороги на DOM-слой и адаптеры
|
|
144
277
|
- 190+ Playwright-сценариев на Chromium, WebKit и мобильной эмуляции: drag, ресайз во все стороны, снэп, магнетизм, рабочие столы, undo после drag, клавиатура, touch, персист через перезагрузку, стресс на 50 окон, модальные ловушки, axe-аудиты доступности, визуальная регрессия по скриншотам
|
|
145
278
|
- перф-бенчмарки в CI на каждый push (`vitest bench`): 1 000 окон открываются за ~150 мс, move среди 50 окон ~1.2 мкс, полный undo/redo-проход на 100 шагов ~52 мкс
|
|
146
279
|
- `publint` + `@arethetypeswrong/cli` проверяют валидность пакета, `size-limit` следит за бюджетами
|
package/dist/angular.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkGLBVT4AQ_cjs = require('./chunk-GLBVT4AQ.cjs');
|
|
4
4
|
var core = require('@angular/core');
|
|
5
5
|
|
|
6
6
|
function onDestroy(dispose) {
|
|
@@ -13,7 +13,7 @@ function onDestroy(dispose) {
|
|
|
13
13
|
destroyRef?.onDestroy(dispose);
|
|
14
14
|
}
|
|
15
15
|
function useWindowManager(options) {
|
|
16
|
-
const wm =
|
|
16
|
+
const wm = chunkGLBVT4AQ_cjs.createWindowManager(options);
|
|
17
17
|
onDestroy(() => wm.destroy());
|
|
18
18
|
return wm;
|
|
19
19
|
}
|
|
@@ -28,7 +28,7 @@ function useWmWindow(wm, id) {
|
|
|
28
28
|
return core.computed(() => state().windows[id]);
|
|
29
29
|
}
|
|
30
30
|
function createDesktop(wm, options) {
|
|
31
|
-
const binder =
|
|
31
|
+
const binder = chunkGLBVT4AQ_cjs.createDesktopBinder(wm, options);
|
|
32
32
|
onDestroy(() => binder.destroy());
|
|
33
33
|
return {
|
|
34
34
|
binder,
|
package/dist/angular.js
CHANGED
|
@@ -209,6 +209,7 @@ function createWindowManager(options = {}) {
|
|
|
209
209
|
let future = [];
|
|
210
210
|
let interactionDepth = 0;
|
|
211
211
|
let interactionRecorded = false;
|
|
212
|
+
let interactionMark = null;
|
|
212
213
|
let skipNextHistory = false;
|
|
213
214
|
let pendingHistory = null;
|
|
214
215
|
const layouts = /* @__PURE__ */ new Map();
|
|
@@ -319,7 +320,13 @@ function createWindowManager(options = {}) {
|
|
|
319
320
|
width: init.width ?? defaultSize.width,
|
|
320
321
|
height: init.height ?? defaultSize.height
|
|
321
322
|
};
|
|
322
|
-
const size = aspectRatio === null ? clampSize(requested, minSize, maxSize) : applyAspect(
|
|
323
|
+
const size = aspectRatio === null ? clampSize(requested, minSize, maxSize) : applyAspect(
|
|
324
|
+
requested,
|
|
325
|
+
aspectRatio,
|
|
326
|
+
minSize,
|
|
327
|
+
maxSize,
|
|
328
|
+
init.width === void 0 && init.height !== void 0 ? "height" : "width"
|
|
329
|
+
);
|
|
323
330
|
const position = positionForOpen(init);
|
|
324
331
|
let bounds = { ...position, ...size };
|
|
325
332
|
if (keepInViewport) bounds = clampToViewport(bounds, viewport, minVisible);
|
|
@@ -435,7 +442,13 @@ function createWindowManager(options = {}) {
|
|
|
435
442
|
}
|
|
436
443
|
function snapBounds(win, zone) {
|
|
437
444
|
const rect = zoneBounds(zone, viewport);
|
|
438
|
-
|
|
445
|
+
const byWidth = normalizeSize(rect, win);
|
|
446
|
+
const size = byWidth.height > rect.height ? normalizeSize(rect, win, "height") : byWidth;
|
|
447
|
+
return {
|
|
448
|
+
x: Math.max(0, Math.min(rect.x, viewport.width - size.width)),
|
|
449
|
+
y: Math.max(0, Math.min(rect.y, viewport.height - size.height)),
|
|
450
|
+
...size
|
|
451
|
+
};
|
|
439
452
|
}
|
|
440
453
|
function focusUnlessBlocked(id) {
|
|
441
454
|
const modal = topModalId();
|
|
@@ -600,7 +613,7 @@ function createWindowManager(options = {}) {
|
|
|
600
613
|
snappable: patch.snappable ?? win.snappable,
|
|
601
614
|
meta: patch.meta ? { ...win.meta, ...patch.meta } : win.meta
|
|
602
615
|
};
|
|
603
|
-
const size = normalizeSize(next.bounds, next);
|
|
616
|
+
const size = next.stage === "maximized" ? next.bounds : normalizeSize(next.bounds, next);
|
|
604
617
|
const resized = size.width !== next.bounds.width || size.height !== next.bounds.height;
|
|
605
618
|
const finalWin = resized ? { ...next, bounds: { ...next.bounds, ...size } } : next;
|
|
606
619
|
setWindow(finalWin);
|
|
@@ -648,6 +661,10 @@ function createWindowManager(options = {}) {
|
|
|
648
661
|
if (!onActiveWorkspace(updated) && focusedId === id) {
|
|
649
662
|
focusTop();
|
|
650
663
|
if (focusedId) emitFocus(windows[focusedId], id);
|
|
664
|
+
} else if (onActiveWorkspace(updated) && topModalId() === id && focusedId !== id) {
|
|
665
|
+
const previous = focusedId;
|
|
666
|
+
focusedId = id;
|
|
667
|
+
emitFocus(updated, previous);
|
|
651
668
|
}
|
|
652
669
|
queueEvent(() => emitter.emit("update", { window: updated }));
|
|
653
670
|
commit();
|
|
@@ -775,14 +792,20 @@ function createWindowManager(options = {}) {
|
|
|
775
792
|
}
|
|
776
793
|
function beginInteraction() {
|
|
777
794
|
interactionDepth += 1;
|
|
778
|
-
if (interactionDepth === 1)
|
|
795
|
+
if (interactionDepth === 1) {
|
|
796
|
+
interactionRecorded = false;
|
|
797
|
+
interactionMark = { past: [...past], future };
|
|
798
|
+
}
|
|
779
799
|
}
|
|
780
800
|
function endInteraction() {
|
|
781
801
|
if (interactionDepth > 0) interactionDepth -= 1;
|
|
802
|
+
if (interactionDepth === 0) interactionMark = null;
|
|
782
803
|
}
|
|
783
804
|
function abortInteraction() {
|
|
784
|
-
if (interactionDepth > 0 &&
|
|
785
|
-
past.
|
|
805
|
+
if (interactionDepth > 0 && interactionMark) {
|
|
806
|
+
past.length = 0;
|
|
807
|
+
past.push(...interactionMark.past);
|
|
808
|
+
future = interactionMark.future;
|
|
786
809
|
interactionRecorded = false;
|
|
787
810
|
}
|
|
788
811
|
endInteraction();
|
|
@@ -1130,10 +1153,8 @@ function createAnnouncer(wm, container, messages = {}) {
|
|
|
1130
1153
|
element.style.cssText = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0";
|
|
1131
1154
|
container.append(element);
|
|
1132
1155
|
let clearTimer;
|
|
1133
|
-
let lastMessage = "";
|
|
1134
1156
|
let detailed = false;
|
|
1135
1157
|
function announce(message) {
|
|
1136
|
-
lastMessage = message;
|
|
1137
1158
|
element.textContent = message;
|
|
1138
1159
|
if (clearTimer !== void 0) clearTimeout(clearTimer);
|
|
1139
1160
|
clearTimer = setTimeout(() => {
|
|
@@ -1141,8 +1162,14 @@ function createAnnouncer(wm, container, messages = {}) {
|
|
|
1141
1162
|
}, 2e3);
|
|
1142
1163
|
}
|
|
1143
1164
|
const unsubscribers = [
|
|
1144
|
-
wm.on("open", ({ window: win }) =>
|
|
1145
|
-
|
|
1165
|
+
wm.on("open", ({ window: win }) => {
|
|
1166
|
+
announce(dict.opened(win.title));
|
|
1167
|
+
detailed = true;
|
|
1168
|
+
}),
|
|
1169
|
+
wm.on("close", ({ window: win }) => {
|
|
1170
|
+
announce(dict.closed(win.title));
|
|
1171
|
+
detailed = true;
|
|
1172
|
+
}),
|
|
1146
1173
|
wm.on("stage", ({ window: win, previous }) => {
|
|
1147
1174
|
if (win.stage === "minimized") announce(dict.minimized(win.title));
|
|
1148
1175
|
else if (win.stage === "maximized") announce(dict.maximized(win.title));
|
|
@@ -1153,7 +1180,7 @@ function createAnnouncer(wm, container, messages = {}) {
|
|
|
1153
1180
|
detailed = true;
|
|
1154
1181
|
}),
|
|
1155
1182
|
wm.on("focus", ({ window: win, previous }) => {
|
|
1156
|
-
if (detailed || previous === null
|
|
1183
|
+
if (detailed || previous === null) return;
|
|
1157
1184
|
announce(dict.focused(win.title));
|
|
1158
1185
|
}),
|
|
1159
1186
|
wm.on("workspace", ({ workspace }) => {
|
|
@@ -1400,6 +1427,7 @@ function createResizeStarter(ctx) {
|
|
|
1400
1427
|
let pendingX = 0;
|
|
1401
1428
|
let pendingY = 0;
|
|
1402
1429
|
let hasPending = false;
|
|
1430
|
+
let applied = false;
|
|
1403
1431
|
const el = ctx.windowElement(id);
|
|
1404
1432
|
if (el) el.dataset.wmResizing = direction;
|
|
1405
1433
|
function flush() {
|
|
@@ -1408,18 +1436,24 @@ function createResizeStarter(ctx) {
|
|
|
1408
1436
|
raf = 0;
|
|
1409
1437
|
const dx = pendingX - startPoint.x;
|
|
1410
1438
|
const dy = pendingY - startPoint.y;
|
|
1411
|
-
const
|
|
1439
|
+
const floor = Math.min(0, start.y);
|
|
1440
|
+
const top = direction.includes("n") ? Math.max(floor, start.y + dy) : start.y;
|
|
1412
1441
|
const raw = {
|
|
1413
1442
|
width: direction.includes("e") ? start.width + dx : direction.includes("w") ? start.width - dx : start.width,
|
|
1414
1443
|
height: direction.includes("s") ? start.height + dy : direction.includes("n") ? start.y + start.height - top : start.height
|
|
1415
1444
|
};
|
|
1416
|
-
|
|
1445
|
+
let size = aspect === null ? clampSize(raw, minSize, maxSize) : applyAspect(raw, aspect, minSize, maxSize, drive);
|
|
1446
|
+
const available = start.y + start.height - floor;
|
|
1447
|
+
if (direction.includes("n") && size.height > available) {
|
|
1448
|
+
size = applyAspect({ ...size, height: available }, aspect ?? 0, minSize, maxSize, "height");
|
|
1449
|
+
}
|
|
1417
1450
|
const next = {
|
|
1418
1451
|
x: direction.includes("w") ? start.x + start.width - size.width : start.x,
|
|
1419
1452
|
y: direction.includes("n") ? start.y + start.height - size.height : start.y,
|
|
1420
1453
|
...size
|
|
1421
1454
|
};
|
|
1422
1455
|
wm.resize(id, next);
|
|
1456
|
+
applied = true;
|
|
1423
1457
|
}
|
|
1424
1458
|
function onMove(moveEvent) {
|
|
1425
1459
|
if (moveEvent.pointerId !== event.pointerId) return;
|
|
@@ -1434,7 +1468,7 @@ function createResizeStarter(ctx) {
|
|
|
1434
1468
|
try {
|
|
1435
1469
|
if (raf !== 0) view.cancelAnimationFrame(raf);
|
|
1436
1470
|
if (hasPending && !cancelled) flush();
|
|
1437
|
-
if (cancelled) {
|
|
1471
|
+
if (cancelled && applied) {
|
|
1438
1472
|
if (startStage === "snapped" && startZone) {
|
|
1439
1473
|
wm.restoreTo(id, startRestore ?? start);
|
|
1440
1474
|
wm.snap(id, startZone);
|
|
@@ -1504,6 +1538,7 @@ var SNAP_SHORTCUTS = {
|
|
|
1504
1538
|
ArrowLeft: "left",
|
|
1505
1539
|
ArrowRight: "right"
|
|
1506
1540
|
};
|
|
1541
|
+
var LANDMARK_TAGS = /* @__PURE__ */ new Set(["header", "footer", "aside", "nav"]);
|
|
1507
1542
|
var FOCUSABLE_SELECTOR = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
1508
1543
|
function attachDesktop(wm, element, options = {}) {
|
|
1509
1544
|
const doc = element.ownerDocument;
|
|
@@ -1635,6 +1670,11 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1635
1670
|
if (hide && !el.hidden && el.contains(doc.activeElement)) {
|
|
1636
1671
|
element.focus({ preventScroll: true });
|
|
1637
1672
|
}
|
|
1673
|
+
if (!hide && el.hidden && wm.getState().focusedId === win.id) {
|
|
1674
|
+
queueMicrotask(() => {
|
|
1675
|
+
if (!el.hidden && !el.contains(doc.activeElement)) el.focus({ preventScroll: true });
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1638
1678
|
el.hidden = hide;
|
|
1639
1679
|
el.setAttribute("aria-label", win.title);
|
|
1640
1680
|
if (win.layer === "modal") el.setAttribute("aria-modal", "true");
|
|
@@ -1683,6 +1723,7 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1683
1723
|
wm.on("focus", ({ window: win }) => {
|
|
1684
1724
|
const attached = registry.get(win.id);
|
|
1685
1725
|
if (!attached) return;
|
|
1726
|
+
if (attached.element.hidden) syncAll();
|
|
1686
1727
|
if (!attached.element.contains(doc.activeElement)) {
|
|
1687
1728
|
attached.element.focus({ preventScroll: true });
|
|
1688
1729
|
}
|
|
@@ -1718,6 +1759,7 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1718
1759
|
return;
|
|
1719
1760
|
}
|
|
1720
1761
|
if (!event.ctrlKey && !event.metaKey) return;
|
|
1762
|
+
if (drag) return;
|
|
1721
1763
|
const target = event.target;
|
|
1722
1764
|
if (target?.closest(INTERACTIVE_SELECTOR)) return;
|
|
1723
1765
|
if (historyShortcuts && (event.key === "z" || event.key === "Z")) {
|
|
@@ -1778,6 +1820,9 @@ function attachDesktop(wm, element, options = {}) {
|
|
|
1778
1820
|
}
|
|
1779
1821
|
const handle = typeof windowOptions.handle === "string" ? windowElement.querySelector(windowOptions.handle) : windowOptions.handle ?? windowElement.querySelector("[data-wm-drag]");
|
|
1780
1822
|
attached.handle = handle;
|
|
1823
|
+
if (handle && !handle.hasAttribute("role") && LANDMARK_TAGS.has(handle.localName)) {
|
|
1824
|
+
handle.setAttribute("role", "presentation");
|
|
1825
|
+
}
|
|
1781
1826
|
const onPointerDownFocus = () => {
|
|
1782
1827
|
wm.focus(id);
|
|
1783
1828
|
};
|
|
@@ -1983,5 +2028,5 @@ function createDesktopBinder(wm, options = {}) {
|
|
|
1983
2028
|
}
|
|
1984
2029
|
|
|
1985
2030
|
export { applyAspect, attachDesktop, boundsEqual, clamp, clampSize, clampToViewport, createAnnouncer, createDesktopBinder, createEmitter, createWindowManager, defaultMessages, detectSnapZone, flipFromTarget, flipToTarget, magnetize, prefersReducedMotion, zoneBounds };
|
|
1986
|
-
//# sourceMappingURL=chunk-
|
|
1987
|
-
//# sourceMappingURL=chunk-
|
|
2031
|
+
//# sourceMappingURL=chunk-GGDF2EWV.js.map
|
|
2032
|
+
//# sourceMappingURL=chunk-GGDF2EWV.js.map
|