# wmkit **Headless оконный менеджер для веба.** Перетаскиваемые окна с ресайзом, снэпом, таскбаром, клавиатурной доступностью и персистом состояния — для vanilla JS и всех основных фреймворков. [English version](./README.md) · [Живое демо](https://wmkit.vercel.app) · [Зеркало (Pages)](https://surdeddd.github.io/wmkit/) · [GitHub](https://github.com/Surdeddd/wmkit) [![wmkit — живой демо-десктоп](https://raw.githubusercontent.com/Surdeddd/wmkit/main/.github/assets/hero.png)](https://surdeddd.github.io/wmkit/)

Все окна выше настоящие — откройте демо и потаскайте.

- 🪟 **Полный жизненный цикл окна** — открытие, закрытие, фокус, сворачивание, разворачивание, восстановление, drag, ресайз в 8 направлениях - 🧠 **Headless-ядро** — сериализуемая стейт-машина плюс DOM-контроллер; своя разметка или готовая стеклянная тема - ⚛️ **Родные адаптеры** — `@surdeddd/wmkit/react`, `/vue`, `/svelte`, `/solid`, `/angular`, тонкий сахар над одним ядром - ⊞ **Snap-зоны** — половины, четверти и максимизация от верхнего края с живым превью - 🧲 **Магнетизм** — края окна прилипают к соседям и вьюпорту при перетаскивании - ↩️ **Undo/redo** — каждая мутация = один шаг; целый drag схлопывается в одну запись истории - 🗂️ **Именованные layout'ы и arrange** — снапшоты рабочего стола, `cascade`/`tile` одним вызовом - ⌨️ **Доступность** — move/resize с клавиатуры, цикл окон по F6, focus-trap в модалках, `aria-live`-анонсы - ⚡ **Производительность** — позиционирование только через `transform`, rAF-батчинг ввода, structural sharing; 50 окон таскаются на 60fps - 💾 **Персист** — один вызов сериализует рабочий стол, один — восстанавливает - 🎨 **Три темы** — тёмное стекло, светлое стекло и Win98-ретро, либо полностью свой CSS - 🖼️ **Popout** *(experimental)* — вынос окна в Document Picture-in-Picture - 📦 **Ноль зависимостей**, строгий TypeScript, ESM + CJS, ~9.3 kB brotli ## Установка ```bash npm install @surdeddd/wmkit # или pnpm add @surdeddd/wmkit ``` ## Быстрый старт (vanilla) ```js import { createWindowManager, attachDesktop } from '@surdeddd/wmkit' import '@surdeddd/wmkit/themes/glass.css' const wm = createWindowManager() const desktop = attachDesktop(wm, document.querySelector('#desktop')) const win = wm.open({ title: 'Привет', width: 420, height: 280 }) const el = document.createElement('section') el.innerHTML = `
Привет
Что угодно.
` document.querySelector('#desktop').append(el) desktop.attachWindow(win.id, el, { removeOnClose: true }) ``` Элемент рабочего стола становится системой координат. Разметка остаётся вашей — wmkit вешает поведение на `data-wm-*` атрибуты: | Атрибут | Смысл | | --- | --- | | `data-wm-drag` | ручка перетаскивания (обычно тайтлбар); двойной клик — toggle maximize | | `data-wm-title` | узел заголовка, связывается через `aria-labelledby` | | `data-wm-close` / `data-wm-minimize` / `data-wm-maximize` | кнопки управления, работают через делегирование | | `data-wm-content` | скроллируемая область контента | `removeOnClose` сам отвязывает контроллер и удаляет элемент при закрытии окна. На тач-устройствах хит-зоны ресайза и порог снэпа автоматически крупнее (`pointer: coarse`); настраиваются через `attachDesktop(wm, el, { hitAreas: { edge, corner } })`. Контроллер добавляет ресайз-хендлы (`[data-wm-resize]`), превью снэпа (`[data-wm-snap-preview]`) и скрытый live-регион для скринридеров. ## Адаптеры Примеры для React, Vue, Svelte, Solid и Angular — в [английском README](./README.md#react) и на [лендинге](https://surdeddd.github.io/wmkit/) (табы «Фреймворки»). Принцип один: контент окна живёт в дереве вашего фреймворка, никакого innerHTML. ## API ядра — кратко ```ts const wm = createWindowManager({ keepInViewport: true, defaultSize: { width: 480, height: 320 } }) wm.open({ id: 'docs', title: 'Документы', layer: 'floating' }) wm.snap('docs', 'left') // 'right' | 'top-left' | 'bottom-right' | … wm.minimize('docs') // restore вернёт предыдущий stage, включая maximized/snapped wm.update('docs', { title: 'Новый заголовок', meta: { pinned: true } }) const json = wm.serialize() // JSON-безопасный снапшот wm.hydrate(json) wm.on('stage', ({ window, previous }) => console.log(previous, '→', window.stage)) wm.batch(() => { /* много операций — одно уведомление */ }) wm.undo(); wm.redo() // история изменений, drag = одна запись (historyLimit, default 50) wm.saveLayout('работа'); wm.loadLayout('работа') // именованные снапшоты рабочего стола wm.arrange('tile') // или 'cascade'; плюс minimizeAll() / restoreAll() ``` Слои: `normal` < `floating` (always-on-top) < `modal`. Модалка блокирует фокус нижних окон (попытка — событие `modalblocked` и flash-анимация), Tab заперт внутри. Клавиатура по умолчанию: стрелки двигают сфокусированное окно (16 px), `Alt` — шаг 1 px, `Shift+стрелки` — ресайз, `F6`/`Shift+F6` — цикл по окнам, `Escape` отменяет активный drag/resize. Магнетизм включён из коробки (порог 8 px, на тач-устройствах 12 px): `attachDesktop(wm, el, { magnetism: { threshold: 16 } })` или `magnetism: false`. Свой контекст-меню тайтлбара — через `onTitlebarContextMenu(win, event)` (правый клик и long-press на таче). ### Персист ```js import { persist } from '@surdeddd/wmkit/persist' persist(wm, { key: 'my-desktop' }) // авто-восстановление + debounce-сохранение ``` ### Popout (experimental) ```js import { popout, isPopoutSupported } from '@surdeddd/wmkit/popout' if (isPopoutSupported()) await popout(wm, 'docs', contentElement) ``` Окно уезжает в настоящее always-on-top окно ОС (Document Picture-in-Picture) с тем же JS-контекстом и состоянием. ## Темизация Подключите `@surdeddd/wmkit/themes/glass.css` и переопределяйте CSS-переменные (`--wm-radius`, `--wm-bg`, `--wm-accent`, …) — или не подключайте ничего и стилизуйте `data-wm-stage`, `data-wm-focused`, `data-wm-dragging`, `[hidden]` сами. Ещё две готовые темы: `themes/light.css` (светлое стекло) и `themes/retro.css` (Win98-ностальгия). Все три стилизуют одни и те же `data-wm-*` атрибуты — переключение = замена одного импорта. ## SSR Ядро не трогает `window`/`document`: менеджер можно создавать и гидрейтить на сервере, `attachDesktop` вызывается после маунта. `persist` тихо выключается без доступного storage. ## Качество - 172 юнит-теста, **100%** покрытие стейт-машины и persist по строкам/веткам/функциям - 178+ Playwright-сценариев на Chromium, WebKit и мобильной эмуляции: drag, ресайз во все стороны, снэп, магнетизм, undo после drag, клавиатура, touch, персист через перезагрузку, стресс на 50 окон, модальные ловушки, axe-аудиты доступности, визуальная регрессия по скриншотам - перф-бенчмарки в CI на каждый push (`vitest bench`): 1 000 окон открываются за ~150 мс, move среди 50 окон ~1.2 мкс, полный undo/redo-проход на 100 шагов ~52 мкс - `publint` + `@arethetypeswrong/cli` проверяют валидность пакета, `size-limit` следит за бюджетами ## Лицензия [MIT](./LICENSE) © Максим Кравцов