zumly 0.9.11 → 0.92.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 CHANGED
@@ -1,26 +1,51 @@
1
1
  <p align="center">
2
- <a href="https://zumly.org">
3
- <img src="https://raw.githubusercontent.com/zumly/website/gh-pages/images/logo-zumly.png" width="200">
2
+ <a href="https://github.com/zumerlab/zumly">
3
+ <!-- Built from docs/zumly-logo.png in this repo; raw URL so the image works on npm too -->
4
+ <img src="https://raw.githubusercontent.com/zumerlab/zumly/main/docs/zumly-logo.png" alt="Zumly" width="200">
4
5
  </a>
5
6
  </p>
6
7
 
8
+ <h1 align="center">Z over XY</h1>
9
+
10
+ <p align="center"><strong>Focus-driven navigation.</strong></p>
11
+ <p align="center"><strong>Zoom into what matters.</strong></p>
12
+
7
13
  <p align="center">
8
- Zumly is a Javascript library for building zooming user interfaces. Create zooming experiences using web standards.
14
+ <a href="https://www.npmjs.com/package/zumly"><img src="https://img.shields.io/npm/v/zumly.svg" alt="npm version"></a>
9
15
  </p>
10
16
 
11
17
  <p align="center">
12
- <a href="https://www.npmjs.com/package/zumly"><img src="https://img.shields.io/npm/v/zumly.svg"></a>
18
+ <strong>Zumly</strong> is a JavaScript library for <strong>hierarchical zoom navigation</strong>: you move in <strong>Z</strong> (depth) through discrete views laid out in the <strong>XY</strong> plane, with spatial transitions instead of flat screen swaps. It is inspired by <a href="https://en.wikipedia.org/wiki/Zooming_user_interface">zoomable user interfaces (ZUI)</a> but targets <strong>structured, trigger-driven</strong> zoom—not infinite pan/zoom canvases.
13
19
  </p>
14
20
 
21
+ ## Status
22
+
23
+ Zumly is under active development. The core stack is stable: depth and lateral navigation, pluggable transition drivers (CSS, WAAPI, none, Anime.js, GSAP, Motion, custom), unified nav UI (depth + lateral, eight positions), view resolver and prefetch cache, optional **plugin** API (<code>.use()</code>), and the **hash router** plugin. View sources include HTML strings, URLs, async functions, objects with <code>render()</code>, DOM nodes, and web component tags.
24
+
25
+ Zoom-out geometry uses batched DOM reads plus pure math where possible to cut layout thrash before animations (see [Geometry optimization](docs/geometry-optimization.md)).
26
+
27
+ **Docs:** [Roadmap & topics](docs/roadMap.md) · [Transition drivers](docs/DRIVER_API.md) · [Geometry notes](docs/geometry-optimization.md)
28
+
15
29
  ## Overview
16
30
 
17
- Zumly is a frontend library for creating zoomable user interfaces ([ZUI](https://en.wikipedia.org/wiki/Zooming_user_interface)). Instead of hyperlinks and windows, Zumly uses zooming as a metaphor for browsing through information. This way it offers an infinite virtual canvas in which elements can be zoomed themselves to reveal further details.
31
+ Unlike free-pan ZUIs, Zumly focuses on **discrete, hierarchical navigation**: users zoom into a focused element (<code>.zoom-me</code>) to open the next view, so **attention** (focus) and **depth** (Z) stay aligned with **layout** (XY).
32
+
33
+ The engine is **UI-agnostic**—you supply markup and CSS. Transforms and timing are handled for you; design systems and frameworks integrate by resolving each view to a DOM subtree (see **View sources** and **Framework integration** below).
18
34
 
19
- To be more flexible Zumly is primarily focused on zooming transitions without caring about visual design. Most CSS frameworks or custom designs work with Zumly.
35
+ ## What Zumly is
36
+
37
+ Zumly is **not** a freeform zooming canvas or map-like navigation system. It is a **discrete, hierarchical zoom interface**: screens are **views** at different depths, connected by **triggers**, with continuous motion between them.
38
+
39
+ It fits especially well when:
40
+
41
+ * you want **focus-driven** flow (zoom into what matters)
42
+ * **spatial context** between parent and child should persist
43
+ * you are building menus, stories, dashboards, or exploratory UIs without a classic router-only metaphor
20
44
 
21
45
  ## Installation
22
46
 
23
47
  ### NPM
48
+
24
49
  ```sh
25
50
  npm install zumly
26
51
 
@@ -29,235 +54,402 @@ npm install zumly
29
54
  yarn add zumly
30
55
  ```
31
56
 
32
- ### Content delivery networks (CDN)
33
- Include https://unpkg.com/zumly in your project in a `<script>` tag.
57
+ ### CDN
34
58
 
59
+ Include Zumly in your project via a `<script>` tag from [unpkg.com/zumly](https://unpkg.com/zumly).
35
60
 
36
61
  ### Direct download
37
62
 
38
- Download Zumly files from [unpkg.com](https://unpkg.com/zumly/). Files are in `dist` folder.
39
-
63
+ Download the built files from [unpkg.com/zumly](https://unpkg.com/zumly/) (see the `dist` folder).
40
64
 
41
65
  ## Setup
42
66
 
67
+ ### Browser bundle (global)
43
68
 
44
- ### ES6 modules
69
+ 1. Add the CSS in your `<head>`:
45
70
 
46
- 1. Add CSS inside `<head>` tag:
47
71
  ```html
48
-
49
72
  <link rel="stylesheet" href="zumly/dist/zumly.css">
73
+ <!-- or https://unpkg.com/zumly/dist/zumly.css -->
74
+ ```
50
75
 
51
- <!-- Or "https://unpkg.com/zumly@0.9.11/dist/zumly.css" -->
76
+ 2. Load the JS bundle (it exposes `window.Zumly`):
52
77
 
78
+ ```html
79
+ <script src="zumly/dist/zumly.js"></script>
80
+ <!-- or https://unpkg.com/zumly/dist/zumly.js -->
53
81
  ```
54
82
 
55
- 2. Add Zumly as ES6 module:
56
- ```html
57
- <script type="module">
58
- import Zumly from "zumly/dist/zumly.mjs"
83
+ ## Hello World
84
+
85
+ 1. Add a container with the class `zumly-canvas`:
59
86
 
60
- // Or "https://unpkg.com/zumly@0.9.11/dist/zumly.mjs"
61
- </script>
87
+ ```html
88
+ <div class="example zumly-canvas"></div>
62
89
  ```
63
90
 
64
- ### UMD modules
91
+ 2. Create your views and start Zumly:
65
92
 
66
- 1. Add Zumly CSS Styles inside `<head>` tag:
67
- ```html
93
+ ```js
94
+ const hello = `
95
+ <div class="z-view">
96
+ H E L L O <br>
97
+ W <span class="zoom-me" data-to="world">O</span> R L D!
98
+ </div>
99
+ `;
68
100
 
69
- <link rel="stylesheet" href="zumly/dist/zumly.css">
101
+ const world = `
102
+ <div class="z-view">
103
+ <img src="https://raw.githubusercontent.com/zumly/website/gh-pages/images/world.png" alt="World">
104
+ </div>
105
+ `;
70
106
 
71
- <!-- Or "https://unpkg.com/zumly@0.9.11/dist/zumly.css" -->
107
+ const app = new Zumly({
108
+ mount: '.example',
109
+ initialView: 'hello',
110
+ views: { hello, world },
111
+ });
72
112
 
113
+ await app.init();
73
114
  ```
74
115
 
75
- 2. Add Zumly as UMD module:
76
- ```html
116
+ - Live example: [CodePen](https://codepen.io/zircle/pen/yyaXvRN)
117
+
118
+ ### Options
77
119
 
78
- <script src="zumly/dist/zumly.umd.js"></script>
120
+ **Zumly constructor:**
79
121
 
80
- // Or "https://unpkg.com/zumly"
122
+ | Option | Type | Required | Description |
123
+ |--------|------|----------|-------------|
124
+ | `mount` | string | Yes | CSS selector for the canvas element (must have class `zumly-canvas`). |
125
+ | `initialView` | string | Yes | Name of the first view to show. |
126
+ | `views` | object | Yes | Map of view names to view sources (see View sources below). |
127
+ | `preload` | string[] | No | View names to resolve and cache when the app initializes. |
128
+ | `transitions` | object | No | Duration, ease, cover, driver, effects, stagger, hideTrigger for zoom transitions. |
129
+ | `deferred` | boolean | No | Defer content rendering until after animation completes (default: `false`). |
130
+ | `debug` | boolean | No | Enable debug messages (default: `false`). |
131
+ | `lateralNav` | boolean \| object | No | Lateral navigation UI: `{ mode, arrows, dots, keepAlive }`. |
132
+ | `depthNav` | boolean \| object | No | Depth navigation UI: `{ button, indicator }`. |
133
+ | `navPosition` | string | No | Nav bar position preset (default: `'bottom-center'`). |
134
+ | `inputs` | boolean \| object | No | Input methods: `{ click, keyboard, wheel, touch }`. |
135
+ | `componentContext` | object | No | Context passed to component-style views. |
81
136
 
137
+ **Transitions (optional):**
138
+
139
+ ```js
140
+ transitions: {
141
+ driver: 'css', // 'css' | 'waapi' | 'anime' | 'gsap' | 'motion' | 'none' or custom function(spec, onComplete)
142
+ cover: 'width', // or 'height' — how the previous view scales to cover the trigger
143
+ duration: '1s',
144
+ ease: 'ease-in-out',
145
+ effects: ['blur(3px) brightness(0.7)', 'blur(8px) saturate(0)'], // CSS filters for [previous, last] background views
146
+ stagger: 0, // delay (ms) between layers during transition
147
+ hideTrigger: false, // false | true (visibility:hidden) | 'fade' (opacity crossfade)
148
+ // threshold: { enabled: true, duration: 300, commitAt: 0.5 } // parsed but not wired in the engine yet
149
+ }
82
150
  ```
83
151
 
152
+ `transitions.parallax` is accepted for compatibility but **not applied** (reserved; intensity is fixed to `0` in the engine).
84
153
 
85
- ## Hello World
154
+ **Transition drivers:** Zoom animations are handled by a pluggable driver (`transitions.driver`). You can swap implementations without changing app logic. To author your own, see [docs/DRIVER_API.md](docs/DRIVER_API.md) and the `zumly/driver-helpers` export.
86
155
 
87
- 1. Create a container for your Zumly app with `.zumly-canvas`:
156
+ | Driver | Description |
157
+ |--------|-------------|
158
+ | `'css'` (default) | CSS keyframes and `animationend`; uses `zumly.css` variables. |
159
+ | `'waapi'` | Web Animations API (`element.animate()`). No extra dependency. |
160
+ | `'none'` | No animation; applies final state immediately. Useful for tests or instant UX. |
161
+ | `'anime'` | [Anime.js](https://animejs.com/) — requires global `anime` (load from CDN before use). |
162
+ | `'gsap'` | [GSAP](https://greensock.com/gsap/) — requires global `gsap` (load from CDN before use). |
163
+ | `'motion'` | [Motion](https://motion.dev/) — requires global `Motion` (load from CDN before use). |
164
+ | `function(spec, onComplete)` | Custom driver. Receives `{ type, currentView, previousView, lastView, currentStage, duration, ease }` and must call `onComplete()` when done. |
88
165
 
89
- ```html
166
+ Example with instant transitions (e.g. for tests):
90
167
 
91
- <div class="example zumly-canvas"></div>
168
+ ```js
169
+ const app = new Zumly({
170
+ mount: '.canvas',
171
+ initialView: 'home',
172
+ views: { home, detail },
173
+ transitions: { driver: 'none', duration: '0s' },
174
+ });
175
+ ```
176
+
177
+ **Lateral navigation:**
92
178
 
179
+ ```js
180
+ lateralNav: true // mode: 'auto' (default)
181
+ lateralNav: false // disabled
182
+ lateralNav: { mode: 'always' } // always show when siblings exist
183
+ lateralNav: { mode: 'auto', dots: false } // auto mode, no dots
93
184
  ```
94
185
 
95
- 2. Inside `script` tag write this code:
186
+ | Mode | Description |
187
+ |------|-------------|
188
+ | `'auto'` (default) | Shows lateral nav only when the current view doesn't cover the full canvas — preserving spatial context. |
189
+ | `'always'` | Always shows lateral nav when siblings exist, regardless of coverage. |
190
+
191
+ In `'auto'` mode, when a view covers 100% of the canvas the user perceives a new independent space, so the lateral nav is suppressed to avoid a floating control with no visual context.
192
+
193
+ **Navigation position (`navPosition`):**
96
194
 
97
195
  ```js
98
- // Some views
99
- const hello = `
100
- <div class="z-view">
101
- H E L L O <br>
102
- W <span class="zoom-me" data-to="world">O</span> R L D!
103
- </div>
104
- `;
196
+ navPosition: 'bottom-center' // default
197
+ ```
105
198
 
106
- const world = `
199
+ | Preset | Position | Layout |
200
+ |--------|----------|--------|
201
+ | `'bottom-center'` | Bottom center | Horizontal |
202
+ | `'bottom-left'` | Bottom left | Horizontal |
203
+ | `'bottom-right'` | Bottom right | Horizontal |
204
+ | `'top-center'` | Top center | Horizontal |
205
+ | `'top-left'` | Top left | Horizontal |
206
+ | `'top-right'` | Top right | Horizontal |
207
+ | `'middle-left'` | Left center | Vertical |
208
+ | `'middle-right'` | Right center | Vertical |
209
+
210
+ The `'middle-left'` and `'middle-right'` presets render the nav bar vertically with rotated arrows.
211
+
212
+ **Zoomable elements:**
213
+
214
+ - Give the view root the class `z-view`.
215
+ - Add class `zoom-me` and `data-to="viewName"` to the element that triggers zoom-in.
216
+ - Per-trigger overrides via `data-*` attributes:
217
+
218
+ | Attribute | Description |
219
+ |-----------|-------------|
220
+ | `data-to` | **Required.** Target view name. |
221
+ | `data-with-duration` | Override transition duration (e.g. `"2s"`). |
222
+ | `data-with-ease` | Override easing function. |
223
+ | `data-with-cover` | Override cover dimension (`"width"` or `"height"`). |
224
+ | `data-with-stagger` | Override stagger delay in ms (e.g. `"100"`). |
225
+ | `data-with-effects` | Override effects (pipe-separated: `"blur(5px)\|blur(10px)"`). |
226
+ | `data-hide-trigger` | Override hideTrigger (`"fade"` or presence = hide). |
227
+ | `data-deferred` | Override deferred rendering (presence = true). |
228
+ | `data-*` | Any other data attribute becomes a prop in `ViewContext.props`. |
229
+
230
+ ```html
107
231
  <div class="z-view">
108
- <img src="https://raw.githubusercontent.com/zumly/website/gh-pages/images/world.png"/>
232
+ <div class="zoom-me" data-to="detail"
233
+ data-with-duration="2s"
234
+ data-with-ease="ease-in"
235
+ data-with-cover="height"
236
+ data-with-stagger="100"
237
+ data-id="42">
238
+ Zoom in
239
+ </div>
109
240
  </div>
110
- `;
241
+ ```
111
242
 
112
- // Zumly instance
113
- const app = new Zumly({
114
- mount: '.example',
115
- initialView: 'hello',
116
- views: {
117
- hello,
118
- world
119
- }
120
- })
243
+ ### View sources
121
244
 
122
- app.init()
245
+ Each entry in `views` is a **view source**. The resolver detects the type and resolves to a DOM node. Hyphenated view names (e.g. `'my-dashboard'`) are resolved as keys in `views` first; only raw template strings with a hyphen are treated as web components.
123
246
 
124
- ```
247
+ | Type | Example | Cached? |
248
+ |------|---------|---------|
249
+ | **HTML string** | `'<div class="z-view">…</div>'` | Yes (indefinitely) |
250
+ | **URL** | `'/views/detail.html'`, `https://…` | Yes (5 min TTL) |
251
+ | **Async function** | `(ctx) => fetch(...).then(r => r.text())` or return `HTMLElement` | No |
252
+ | **Object with `render()`** | `{ render(ctx) { return '<div>…</div>' }, mounted?() }` | No |
253
+ | **Web component** | `'my-view'` (string with hyphen, not a key in `views`) | No |
125
254
 
126
- - See this example live at [codePen](https://codepen.io/zumly/pen/gOPQovd)
255
+ **View pipeline:** Resolve → normalize `.z-view` insert into canvas call `mounted()` (if present). Static/URL views are cloned from cache on each `get()` so consumers cannot mutate the stored node.
127
256
 
128
- ### Zumly options
257
+ ### Framework integration
129
258
 
130
- 1. The Zumly instance:
259
+ Zumly is framework-agnostic. Since views resolve to DOM elements, any framework that can mount into a container works out of the box. Use **function views** or **object views** to bridge your framework:
260
+
261
+ **React**
262
+
263
+ ```jsx
264
+ import { createRoot } from 'react-dom/client'
265
+ import Dashboard from './Dashboard'
131
266
 
132
- ```js
133
267
  const app = new Zumly({
134
- // Mount DOM Element. String. Required
135
- mount: '.className',
136
- // First rendered view name. String. Required
137
- initialView: 'viewName',
138
- // Store all views. Object. Required
268
+ mount: '.canvas',
269
+ initialView: 'home',
139
270
  views: {
140
- view1,
141
- view2,
142
- . . .
143
- },
144
- // Customize transitions. Object. Optional
145
- transitions: {
146
- // Effects for background views. Array. ['blur', 'sepia', 'saturate']
147
- effects: ['sepia'],
148
- // How new injected view is adapted. String. Default 'width'
149
- cover: 'height',
150
- // Transition duration. String. Default '1s'
151
- duration: '1300ms' ,
152
- // Transition ease. String. Default 'ease-in-out'
153
- ease: 'cubic-bezier(0.25,0.1,0.25,1)'
154
- },
155
- // Activate debug notifications. Boolean. Default false
156
- debug: true
271
+ home: '<div class="z-view"><div class="zoom-me" data-to="dashboard" data-id="42">Open</div></div>',
272
+ dashboard: ({ target, props }) => {
273
+ const root = createRoot(target)
274
+ root.render(<Dashboard id={props.id} />)
275
+ }
276
+ }
157
277
  })
158
- // Initialize instance
159
- app.init()
160
278
  ```
161
279
 
162
- 2. Options for each zoomable element:
280
+ **Vue**
163
281
 
164
- - Add `z-view` class in you view container:
282
+ ```js
283
+ import { createApp } from 'vue'
284
+ import Dashboard from './Dashboard.vue'
165
285
 
166
- ```html
286
+ views: {
287
+ dashboard: ({ target, props }) => {
288
+ createApp(Dashboard, { id: props.id }).mount(target)
289
+ }
290
+ }
291
+ ```
292
+
293
+ **Svelte**
167
294
 
168
- <div class="z-view"></div>
295
+ ```js
296
+ import Dashboard from './Dashboard.svelte'
169
297
 
298
+ views: {
299
+ dashboard: ({ target, props }) => {
300
+ new Dashboard({ target, props: { id: props.id } })
301
+ }
302
+ }
170
303
  ```
171
304
 
172
- - Add `zoom-me` class to an HTML element to make it zoomable and add `data-to` attribute with the name of the target view
305
+ **Angular**
173
306
 
174
- ```html
307
+ ```ts
308
+ views: {
309
+ dashboard: ({ target, props }) => {
310
+ const compRef = viewContainerRef.createComponent(DashboardComponent)
311
+ compRef.instance.id = props.id
312
+ target.appendChild(compRef.location.nativeElement)
313
+ }
314
+ }
315
+ ```
316
+
317
+ **Key points:**
318
+
319
+ - The `target` parameter is a fresh `<div>` created by Zumly — mount your component there.
320
+ - `props` contains data attributes from the trigger element (`data-id="42"` → `props.id`).
321
+ - `componentContext` (constructor option) is passed as `context` to all function/object views — use it for shared state (router, store, API client).
322
+ - Function views are **never cached** — they resolve fresh each time, so framework components get proper lifecycle management.
323
+ - Use `mounted()` (object views) for post-insertion setup — it runs after the node is in the DOM.
324
+ - Zumly handles wrapped elements (e.g. Svelte's extra parent div) in its cleanup logic.
175
325
 
176
- <div class="zoom-me" data-to="anotherView">Zoom me!</div>
326
+ ### Preload and prefetch
177
327
 
328
+ - **Eager preload:** `preload: ['viewA', 'viewB']` — those views are resolved and cached during `init()`.
329
+ - **Hover prefetch:** `mouseover` on a `.zoom-me[data-to]` trigger prefetches its target in the background.
330
+ - **Focus prefetch:** `focusin` on a `.zoom-me[data-to]` also prefetches (for keyboard/accessibility).
331
+ - **Scan prefetch:** When a view becomes current, all `.zoom-me[data-to]` targets inside it are prefetched in the background. This works on touch devices where hover is unavailable.
332
+
333
+ ### Plugins
334
+
335
+ Zumly has a lightweight plugin system. Register plugins with `.use()` before or after `init()`:
336
+
337
+ ```js
338
+ app.use(plugin, options)
178
339
  ```
179
340
 
180
- - Each zooming transition can be customized by adding some `data-` attributes:
341
+ A plugin is an object with `install(instance, options)` or a plain function `(instance, options) => void`.
181
342
 
182
- ```html
343
+ #### Router plugin
183
344
 
184
- <div class="zoom-me" data-to="anotherView" data-with-duration="2s" data-with-ease="ease-in">
185
- Zoom me!
186
- </div>
345
+ Syncs the browser URL hash with Zumly's navigation state. Browser back triggers zoom-out or lateral navigation. Forward is intentionally blocked — in a ZUI, zoom-in requires a trigger element for proper origin and animation context.
187
346
 
347
+ ```js
348
+ // Script tag
349
+ const app = new Zumly({ ... })
350
+ app.use(Zumly.Router)
351
+ await app.init()
352
+
353
+ // ES Module (named export from the package entry)
354
+ import { Zumly, ZumlyRouter } from 'zumly'
355
+ const app = new Zumly({ ... })
356
+ app.use(ZumlyRouter)
357
+ await app.init()
188
358
  ```
189
359
 
190
- ## Development
360
+ The UMD/IIFE bundle attaches the same plugin as `Zumly.Router`. There is no separate published subpath for the router; import it from `'zumly'` or use `Zumly.Router` on `window` when using a script tag.
191
361
 
192
- ### Developer environment requirements
362
+ **Options:**
193
363
 
194
- To run this project, you will need:
364
+ | Option | Type | Default | Description |
365
+ |--------|------|---------|-------------|
366
+ | `separator` | string | `'/'` | Character used to join view path segments in the hash. |
367
+ | `prefix` | string | `'/'` | Prefix before the path in the hash. |
195
368
 
196
- - Node.js >= v10.5.0
369
+ **Behavior:**
197
370
 
198
- ### Dev mode
371
+ | Action | Hash update | History |
372
+ |--------|-------------|---------|
373
+ | Zoom in | `pushState` | Enables browser back |
374
+ | Lateral | `pushState` | Enables browser back |
375
+ | Zoom out | `replaceState` | No forward entry |
376
+ | Browser back | Triggers `zoomOut()` or lateral `goTo()` | — |
377
+ | Browser forward | Blocked (`history.back()`) | — |
199
378
 
200
- When developing you can run:
379
+ **Example URL:** `#/home/showcases/mercedes`
201
380
 
202
- ```sh
203
- npm run dev
381
+ ### Limitations and non-goals
204
382
 
205
- # or
383
+ - **No deep-linking:** The router plugin syncs hash on navigation and supports browser back, but does not support forward or deep-linking (entering a multi-level URL directly). In a ZUI, zoom-in requires a trigger element for proper spatial context.
384
+ - **Resize handling:** Cheap correction when canvas resizes — translate and origin scaled by ratio; scale preserved. Correction is deferred if a transition is running.
385
+ - **Remote views:** URL-backed views use `innerHTML`; sanitize external content to avoid XSS.
206
386
 
207
- yarn dev
208
- ```
387
+ ## Development
388
+
389
+ ### Requirements
209
390
 
210
- This will regenerate the build files each time a source file is changed and serve on http://localhost:9090
391
+ - Node.js >= 18 (or 16+ with ES module support)
211
392
 
212
- ### Running tests
393
+ ### Commands
213
394
 
214
395
  ```sh
396
+ # Build the library
397
+ npm run compile
398
+
399
+ # Build and serve the demo at http://localhost:9090
400
+ npm run dev
401
+
402
+ # Run tests (Vitest + Playwright). Install browsers first:
403
+ npm run test:install-browsers
215
404
  npm run test
216
405
 
217
- # or
406
+ # Run tests with coverage
407
+ npm run test:coverage
218
408
 
219
- yarn test
409
+ # Build and pack for publish
410
+ npm run build
220
411
  ```
221
412
 
413
+ Tests use [Vitest](https://vitest.dev/) with the browser provider ([Playwright](https://playwright.dev/)), same setup as [SnapDOM](https://github.com/zumerlab/snapdom). Run `npm run test:install-browsers` once (or after upgrading Playwright) to install Chromium.
414
+
222
415
  ### Building
223
416
 
224
417
  ```sh
225
- npm run build
226
-
227
- # or
228
-
229
- yarn build
418
+ npm run compile
230
419
  ```
231
420
 
232
- ## Changelog
421
+ Output is in the `dist/` folder.
233
422
 
234
- Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
423
+ ## Changelog
235
424
 
236
- ### Status: beta
425
+ See [CHANGELOG.md](CHANGELOG.md) for version history.
237
426
 
238
- Zumly is on early stages of development.
427
+ ## Roadmap
239
428
 
240
- ### Roadmap
429
+ **Done:**
430
+ - Depth and lateral navigation (`zoomIn`, `zoomOut`, `goTo`, `back`, `zoomTo`)
431
+ - Lateral nav UI (`lateralNav`: `mode` auto/always, arrows, dots, `keepAlive`)
432
+ - Depth nav UI (`depthNav`) and `navPosition` presets
433
+ - Inputs toggles (`inputs`: wheel, keyboard, click, touch)
434
+ - Plugin system (`use()`), router plugin (hash sync, back, forward blocked)
435
+ - Resize correction (translate/origin scaling; deferred while transitioning)
436
+ - Pluggable drivers (CSS, WAAPI, none, Anime.js, GSAP, Motion, custom)
437
+ - Batched zoom-out reads + math helpers to reduce reflow (see [geometry-optimization.md](docs/geometry-optimization.md))
241
438
 
242
- - Allow different template engines. Currently Zumly only accepts string literal templates.
243
- - Add lateral navigation for same zoom level elements.
244
- - Add a navegation widget.
245
- - Add programmatic navigation.
246
- - Add preseted navigation.
247
- - Add router. [#3](https://github.com/zumly/zumly/issues/3)
248
- - Allow recalculate zoom position on resize events.
439
+ **Planned:**
440
+ - Router deep-linking (open a multi-level hash cold)
441
+ - Accessibility (focus moves, broader ARIA)
249
442
 
443
+ Details and more topics: [docs/roadMap.md](docs/roadMap.md). Driver contract and helpers: [docs/DRIVER_API.md](docs/DRIVER_API.md).
250
444
 
251
- ## Stay in touch
445
+ ## Community
252
446
 
253
447
  - [Telegram group](https://t.me/ZumlyCommunity)
254
448
 
255
- ## Original idea
449
+ ## Origin
256
450
 
257
- Zumly is a new approach based on another library I made, [Zircle UI](https://github.com/zircleUI/zircleUI)
451
+ Zumly is a reimagined, framework-agnostic zoom engine inspired by [Zircle UI](https://github.com/zircleUI/zircleUI). Part of the [Zumerlab](https://github.com/zumerlab) ecosystem — use it with [Orbit](https://github.com/zumerlab/orbit) for radial layouts and [SnapDOM](https://github.com/zumerlab/snapdom) for lightweight DOM diffing.
258
452
 
259
453
  ## License
260
454
 
261
- The MIT License (MIT). Please see [License File](LICENSE) for more information.
262
-
263
-
455
+ MIT. See [LICENSE](LICENSE).