@marcwiest/midday.js 0.1.0 → 0.2.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 +91 -49
- package/dist/core.d.ts +1 -1
- package/dist/core.mjs +97 -95
- package/dist/index.d.ts +5 -5
- package/dist/midday.mjs +16 -16
- package/dist/midday.umd.js +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/solid.d.ts +1 -1
- package/dist/solid.mjs +4 -4
- package/dist/types.d.ts +5 -5
- package/dist/utils.d.ts +2 -2
- package/dist/vue.d.ts +1 -1
- package/dist/vue.mjs +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/marcwiest/midday.js/actions/workflows/ci.yml)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
A modern, zero-dependency vanilla JS plugin for fixed
|
|
8
|
+
A modern, zero-dependency vanilla JS plugin for fixed elements that change style as you scroll through page sections. The spiritual successor to [midnight.js](https://github.com/Aerolab/midnight.js).
|
|
9
9
|
|
|
10
10
|
**~1 kB gzipped** (auto mode) | TypeScript | Framework adapters (React, Vue, Svelte, Solid) included
|
|
11
11
|
|
|
@@ -36,12 +36,12 @@ Or via CDN (UMD):
|
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
-
### 1.
|
|
39
|
+
### 1. Mark your element, add your sections
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Add `data-midday-element` to your fixed element. Each section declares which variant it wants via `data-midday-section`:
|
|
42
42
|
|
|
43
43
|
```html
|
|
44
|
-
<header data-midday>
|
|
44
|
+
<header data-midday-element>
|
|
45
45
|
<nav>
|
|
46
46
|
<a href="/" class="logo">Logo</a>
|
|
47
47
|
<a href="/about">About</a>
|
|
@@ -67,15 +67,15 @@ You write a single header. Each section declares which header style it wants via
|
|
|
67
67
|
```js
|
|
68
68
|
import { midday } from '@marcwiest/midday.js';
|
|
69
69
|
|
|
70
|
-
const instance = midday(document.querySelector('[data-midday]'));
|
|
70
|
+
const instance = midday(document.querySelector('[data-midday-element]'));
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
### 3. What happens next
|
|
74
74
|
|
|
75
|
-
The plugin reads every unique `data-midday-section` value on the page (`"dark"`, `"accent"`) and clones your
|
|
75
|
+
The plugin reads every unique `data-midday-section` value on the page (`"dark"`, `"accent"`) and clones your element's content once per variant. Each clone is wrapped in a container with a `data-midday-variant` attribute. Your original HTML stays as-is — the clones are created at runtime:
|
|
76
76
|
|
|
77
77
|
```
|
|
78
|
-
<header data-midday>
|
|
78
|
+
<header data-midday-element> ← your element (position: fixed)
|
|
79
79
|
<div data-midday-variant="default"> ← default style (original content)
|
|
80
80
|
<nav>Logo, About, Contact</nav>
|
|
81
81
|
</div>
|
|
@@ -122,19 +122,19 @@ header {
|
|
|
122
122
|
}
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
That's it. Scroll through your sections and the
|
|
125
|
+
That's it. Scroll through your sections and the element transitions smoothly from one variant to another.
|
|
126
126
|
|
|
127
127
|
## API
|
|
128
128
|
|
|
129
|
-
### `midday(
|
|
129
|
+
### `midday(element, options?)` — Auto mode
|
|
130
130
|
|
|
131
|
-
Clones your
|
|
131
|
+
Clones your element's content once per variant and manages everything. Sections are discovered automatically via `data-midday-section` attributes.
|
|
132
132
|
|
|
133
133
|
```js
|
|
134
|
-
const instance = midday(document.querySelector('[data-midday]'));
|
|
134
|
+
const instance = midday(document.querySelector('[data-midday-element]'));
|
|
135
135
|
|
|
136
136
|
// With optional onChange callback:
|
|
137
|
-
const instance = midday(document.querySelector('[data-midday]'), {
|
|
137
|
+
const instance = midday(document.querySelector('[data-midday-element]'), {
|
|
138
138
|
onChange: (variants) => console.log(variants),
|
|
139
139
|
});
|
|
140
140
|
```
|
|
@@ -147,7 +147,7 @@ You provide pre-rendered variant elements. The plugin only manages `clip-path` v
|
|
|
147
147
|
import { middayHeadless } from '@marcwiest/midday.js';
|
|
148
148
|
|
|
149
149
|
const instance = middayHeadless({
|
|
150
|
-
|
|
150
|
+
element: document.querySelector('header'),
|
|
151
151
|
variants: {
|
|
152
152
|
default: document.querySelector('.header-default'),
|
|
153
153
|
dark: document.querySelector('.header-dark'),
|
|
@@ -162,19 +162,36 @@ const instance = middayHeadless({
|
|
|
162
162
|
Both modes return the same instance:
|
|
163
163
|
|
|
164
164
|
```js
|
|
165
|
-
instance.refresh(); //
|
|
165
|
+
instance.refresh(); // Rebuild variants and re-scan sections
|
|
166
166
|
instance.destroy(); // Full teardown — removes clones, listeners, observers
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
+
**When to call `refresh()`** — in both modes, call it when sections are added, removed, or reordered in the DOM. Beyond that, the two modes differ:
|
|
170
|
+
|
|
171
|
+
**Auto mode** clones element content at init time. The clones and an internal sizing ghost are frozen snapshots of the element's DOM. CSS-driven size changes (media queries, viewport resize, font loading) are handled automatically — the sizing ghost is a real DOM node in normal flow and reflows with the page. But if the element's HTML content changes (nav items added/removed, conditional elements toggled), call `refresh()` to rebuild the clones and sizing ghost from the current DOM.
|
|
172
|
+
|
|
173
|
+
Framework adapters initialize once on mount and don't auto-detect content changes. If your element's content is dynamic, call `refresh()` after updates:
|
|
174
|
+
|
|
175
|
+
```jsx
|
|
176
|
+
// React example
|
|
177
|
+
const instance = useMidday(elementRef);
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
instance.current?.refresh();
|
|
181
|
+
}, [navItems]);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Headless mode** doesn't manage element DOM — you own the variant elements. The engine reads element and variant sizes live on every scroll frame via `getBoundingClientRect()`, so size changes to your variant elements are picked up automatically. You only need `refresh()` when sections change, since section bounds are cached.
|
|
185
|
+
|
|
169
186
|
### `onChange` callback
|
|
170
187
|
|
|
171
188
|
Fires whenever the set of visible variants changes:
|
|
172
189
|
|
|
173
190
|
```js
|
|
174
|
-
midday(
|
|
191
|
+
midday(element, {
|
|
175
192
|
onChange: (variants) => {
|
|
176
193
|
// variants: Array<{ name: string, progress: number }>
|
|
177
|
-
// progress: 0–1, how much of the
|
|
194
|
+
// progress: 0–1, how much of the element this variant covers
|
|
178
195
|
console.log(variants);
|
|
179
196
|
// e.g. [{ name: 'dark', progress: 0.7 }, { name: 'default', progress: 0.3 }]
|
|
180
197
|
},
|
|
@@ -185,7 +202,7 @@ midday(header, {
|
|
|
185
202
|
|
|
186
203
|
Each adapter is a separate tree-shakable entry point (~0.2 kB gzipped). Import only the one you need — the others are never bundled.
|
|
187
204
|
|
|
188
|
-
The adapters wrap **auto mode** — your component renders a single
|
|
205
|
+
The adapters wrap **auto mode** — your component renders a single element, and cloning happens client-side on mount. This means your server-rendered HTML stays clean (see [SSR & SEO](#ssr--seo) below).
|
|
189
206
|
|
|
190
207
|
### React
|
|
191
208
|
|
|
@@ -194,11 +211,11 @@ import { useRef } from 'react';
|
|
|
194
211
|
import { useMidday } from '@marcwiest/midday.js/react';
|
|
195
212
|
|
|
196
213
|
function Header() {
|
|
197
|
-
const
|
|
198
|
-
useMidday(
|
|
214
|
+
const elementRef = useRef(null);
|
|
215
|
+
useMidday(elementRef);
|
|
199
216
|
|
|
200
217
|
return (
|
|
201
|
-
<header ref={
|
|
218
|
+
<header ref={elementRef} style={{ position: 'fixed', top: 0, left: 0, right: 0 }}>
|
|
202
219
|
<Nav />
|
|
203
220
|
</header>
|
|
204
221
|
);
|
|
@@ -214,12 +231,12 @@ Composable:
|
|
|
214
231
|
import { ref } from 'vue';
|
|
215
232
|
import { useMidday } from '@marcwiest/midday.js/vue';
|
|
216
233
|
|
|
217
|
-
const
|
|
218
|
-
useMidday(
|
|
234
|
+
const elementRef = ref(null);
|
|
235
|
+
useMidday(elementRef);
|
|
219
236
|
</script>
|
|
220
237
|
|
|
221
238
|
<template>
|
|
222
|
-
<header ref="
|
|
239
|
+
<header ref="elementRef">
|
|
223
240
|
<Nav />
|
|
224
241
|
</header>
|
|
225
242
|
</template>
|
|
@@ -259,11 +276,11 @@ Primitive:
|
|
|
259
276
|
import { createMidday } from '@marcwiest/midday.js/solid';
|
|
260
277
|
|
|
261
278
|
function Header() {
|
|
262
|
-
let
|
|
263
|
-
createMidday(() =>
|
|
279
|
+
let el;
|
|
280
|
+
createMidday(() => el);
|
|
264
281
|
|
|
265
282
|
return (
|
|
266
|
-
<header ref={
|
|
283
|
+
<header ref={el} style={{ position: 'fixed', top: '0', left: '0', right: '0' }}>
|
|
267
284
|
<Nav />
|
|
268
285
|
</header>
|
|
269
286
|
);
|
|
@@ -290,10 +307,10 @@ All adapters accept `onChange`:
|
|
|
290
307
|
|
|
291
308
|
```jsx
|
|
292
309
|
// React
|
|
293
|
-
useMidday(
|
|
310
|
+
useMidday(elementRef, { onChange: (v) => console.log(v) });
|
|
294
311
|
|
|
295
312
|
// Vue (composable)
|
|
296
|
-
useMidday(
|
|
313
|
+
useMidday(elementRef, { onChange: (v) => console.log(v) });
|
|
297
314
|
|
|
298
315
|
// Vue (directive)
|
|
299
316
|
<header v-midday="{ onChange: (v) => console.log(v) }"></header>
|
|
@@ -302,7 +319,7 @@ useMidday(headerRef, { onChange: (v) => console.log(v) });
|
|
|
302
319
|
<header use:midday={{ onChange: (v) => console.log(v) }}></header>
|
|
303
320
|
|
|
304
321
|
// Solid (primitive)
|
|
305
|
-
createMidday(() =>
|
|
322
|
+
createMidday(() => el, { onChange: (v) => console.log(v) });
|
|
306
323
|
|
|
307
324
|
// Solid (directive)
|
|
308
325
|
<header use:midday={{ onChange: (v) => console.log(v) }}></header>
|
|
@@ -310,13 +327,13 @@ createMidday(() => headerEl, { onChange: (v) => console.log(v) });
|
|
|
310
327
|
|
|
311
328
|
## Multiple Instances
|
|
312
329
|
|
|
313
|
-
midday.js supports multiple independent fixed elements on the same page (e.g., a top header and a bottom app-bar). Name each instance via the `data-midday` attribute and use `data-midday-target` on sections to control which instance they affect.
|
|
330
|
+
midday.js supports multiple independent fixed elements on the same page (e.g., a top header and a bottom app-bar). Name each instance via the `data-midday-element` attribute and use `data-midday-target` on sections to control which instance they affect.
|
|
314
331
|
|
|
315
332
|
```html
|
|
316
|
-
<header data-midday="top">...</header>
|
|
317
|
-
<nav class="app-bar" data-midday="bottom">...</nav>
|
|
333
|
+
<header data-midday-element="top">...</header>
|
|
334
|
+
<nav class="app-bar" data-midday-element="bottom">...</nav>
|
|
318
335
|
|
|
319
|
-
<!-- Targets only the top
|
|
336
|
+
<!-- Targets only the top element -->
|
|
320
337
|
<section data-midday-section="accent" data-midday-target="top">...</section>
|
|
321
338
|
|
|
322
339
|
<!-- Targets both (space-separated) -->
|
|
@@ -329,19 +346,19 @@ midday.js supports multiple independent fixed elements on the same page (e.g., a
|
|
|
329
346
|
```js
|
|
330
347
|
import { midday } from '@marcwiest/midday.js';
|
|
331
348
|
|
|
332
|
-
const top = midday(document.querySelector('[data-midday="top"]'));
|
|
333
|
-
const bottom = midday(document.querySelector('[data-midday="bottom"]'));
|
|
349
|
+
const top = midday(document.querySelector('[data-midday-element="top"]'));
|
|
350
|
+
const bottom = midday(document.querySelector('[data-midday-element="bottom"]'));
|
|
334
351
|
```
|
|
335
352
|
|
|
336
|
-
Each instance runs its own engine and only reacts to its own sections. The instance name defaults to the element's `data-midday` attribute value, or you can set it explicitly via `options.name`.
|
|
353
|
+
Each instance runs its own engine and only reacts to its own sections. The instance name defaults to the element's `data-midday-element` attribute value, or you can set it explicitly via `options.name`.
|
|
337
354
|
|
|
338
355
|
## SSR & SEO
|
|
339
356
|
|
|
340
357
|
midday.js is designed to be SSR-safe by default.
|
|
341
358
|
|
|
342
|
-
**Auto mode** (including all framework adapters) clones
|
|
359
|
+
**Auto mode** (including all framework adapters) clones element content client-side on mount. The server-rendered HTML always contains a single, clean element — no duplicate navigation links, no hidden clones. Search engine crawlers see exactly one set of content.
|
|
343
360
|
|
|
344
|
-
After hydration, the plugin creates variant clones in the browser. These clones are marked with `aria-hidden="true"` and `inert`, so they're invisible to screen readers and excluded from keyboard navigation. The original
|
|
361
|
+
After hydration, the plugin creates variant clones in the browser. These clones are marked with `aria-hidden="true"` and `inert`, so they're invisible to screen readers and excluded from keyboard navigation. The original content remains the accessible version.
|
|
345
362
|
|
|
346
363
|
**Headless mode** is different — since you provide the variant elements yourself, they exist in your markup. If you're using headless mode with SSR, render non-default variants client-side only to avoid duplicate content in the server HTML:
|
|
347
364
|
|
|
@@ -352,23 +369,23 @@ import { middayHeadless } from '@marcwiest/midday.js';
|
|
|
352
369
|
|
|
353
370
|
function Header() {
|
|
354
371
|
const [mounted, setMounted] = useState(false);
|
|
355
|
-
const
|
|
372
|
+
const elementRef = useRef(null);
|
|
356
373
|
const defaultRef = useRef(null);
|
|
357
374
|
const darkRef = useRef(null);
|
|
358
375
|
|
|
359
376
|
useEffect(() => setMounted(true), []);
|
|
360
377
|
|
|
361
378
|
useEffect(() => {
|
|
362
|
-
if (!mounted || !
|
|
379
|
+
if (!mounted || !elementRef.current) return;
|
|
363
380
|
const instance = middayHeadless({
|
|
364
|
-
|
|
381
|
+
element: elementRef.current,
|
|
365
382
|
variants: { default: defaultRef.current, dark: darkRef.current },
|
|
366
383
|
});
|
|
367
384
|
return () => instance.destroy();
|
|
368
385
|
}, [mounted]);
|
|
369
386
|
|
|
370
387
|
return (
|
|
371
|
-
<header ref={
|
|
388
|
+
<header ref={elementRef}>
|
|
372
389
|
<div ref={defaultRef} className="header-default"><Nav /></div>
|
|
373
390
|
{mounted && (
|
|
374
391
|
<div ref={darkRef} className="header-dark" aria-hidden="true" inert="">
|
|
@@ -384,23 +401,48 @@ For most use cases, the framework adapters (which use auto mode) are simpler and
|
|
|
384
401
|
|
|
385
402
|
## How It Works
|
|
386
403
|
|
|
387
|
-
midday.js uses `clip-path: inset()` to reveal and hide variant
|
|
404
|
+
midday.js uses `clip-path: inset()` to reveal and hide variant elements as sections scroll past.
|
|
388
405
|
|
|
389
|
-
1. **Auto mode** clones the
|
|
406
|
+
1. **Auto mode** clones the element's content once per unique variant found in `data-midday-section` attributes. Each clone is wrapped in an absolutely-positioned container inside the managed element.
|
|
390
407
|
|
|
391
|
-
2. On each scroll frame, the plugin calculates which sections overlap the
|
|
408
|
+
2. On each scroll frame, the plugin calculates which sections overlap the element's viewport position and by how many pixels.
|
|
392
409
|
|
|
393
|
-
3. Each variant's container gets a `clip-path: inset(topPx 0 bottomPx 0)` that reveals exactly the portion corresponding to its section's overlap with the
|
|
410
|
+
3. Each variant's container gets a `clip-path: inset(topPx 0 bottomPx 0)` that reveals exactly the portion corresponding to its section's overlap with the element. Every variant — including the default — is clipped to only its own region. Nothing is used as a backdrop, so transparent backgrounds work fine.
|
|
394
411
|
|
|
395
412
|
The result is a pixel-perfect wipe transition at every section boundary.
|
|
396
413
|
|
|
397
414
|
## Styling Guide
|
|
398
415
|
|
|
399
|
-
- The
|
|
416
|
+
- The managed element should be `position: fixed` or `position: sticky`
|
|
400
417
|
- In auto mode, variant wrappers get `data-midday-variant="<name>"` — target them with `[data-midday-variant="dark"]` or however you prefer
|
|
401
418
|
- Transparent variant backgrounds work — each variant is clipped independently, so page content shows through where intended
|
|
402
|
-
- In headless mode, you're responsible for positioning variant elements absolutely within the
|
|
403
|
-
- In headless mode, variant elements can have different heights than the
|
|
419
|
+
- In headless mode, you're responsible for positioning variant elements absolutely within the managed element and setting `aria-hidden`/`inert` on non-default variants
|
|
420
|
+
- In headless mode, variant elements can have different heights than the managed element. The clip-path will track section boundaries exactly, with extra height revealing only when the section extends past the element edge
|
|
421
|
+
- Auto mode uses `cloneNode(true)` to create variant copies. This duplicates DOM structure and attributes but **not** JavaScript event listeners attached via `addEventListener`. If your element contains interactive elements (nav links, dropdowns, etc.), use **event delegation** — attach a single listener to `document` and match with `closest()` — so events work in all variants
|
|
422
|
+
|
|
423
|
+
## Overflow Content (Dropdowns, Flyouts)
|
|
424
|
+
|
|
425
|
+
`clip-path: inset(...)` clips **all** descendants, including `position: fixed` elements like dropdown panels. In auto mode, `cloneNode(true)` also duplicates dropdown markup into every variant. There is no library-level fix for this, but the workaround is straightforward:
|
|
426
|
+
|
|
427
|
+
**Keep triggers inside the element; render panels outside the element's DOM entirely.**
|
|
428
|
+
|
|
429
|
+
Position the panel as a sibling of the element (or in a portal container) and align it visually with its trigger. Since triggers get cloned into each variant, use **event delegation** to handle clicks:
|
|
430
|
+
|
|
431
|
+
```js
|
|
432
|
+
document.addEventListener('click', (e) => {
|
|
433
|
+
const trigger = e.target.closest('.dropdown-trigger');
|
|
434
|
+
if (!trigger) return;
|
|
435
|
+
const panel = document.querySelector('#dropdown-panel');
|
|
436
|
+
const rect = trigger.getBoundingClientRect();
|
|
437
|
+
panel.style.top = rect.bottom + 'px';
|
|
438
|
+
panel.style.left = rect.left + 'px';
|
|
439
|
+
panel.classList.toggle('open');
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**CSS Anchor Positioning** offers a progressive enhancement: set `anchor-name` on the trigger and use `position-anchor` + `position: fixed` on the panel. This works across DOM subtrees without JavaScript positioning. Browser support is still limited (Chromium 125+).
|
|
444
|
+
|
|
445
|
+
**Framework portals** solve this idiomatically: React's `createPortal`, Vue's `<Teleport to="body">`, Solid's `<Portal>`, or a Svelte portal library. Render the dropdown panel into `document.body` so it sits outside the clipped element entirely.
|
|
404
446
|
|
|
405
447
|
## Browser Support
|
|
406
448
|
|
package/dist/core.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { MiddayOptions, MiddayInstance } from './types';
|
|
2
|
-
export declare function createMidday(
|
|
2
|
+
export declare function createMidday(element: HTMLElement, options?: MiddayOptions): MiddayInstance;
|
package/dist/core.mjs
CHANGED
|
@@ -14,59 +14,59 @@ function z(t) {
|
|
|
14
14
|
}
|
|
15
15
|
function P(t) {
|
|
16
16
|
for (const i of t) {
|
|
17
|
-
const
|
|
18
|
-
i.top =
|
|
17
|
+
const p = k(i.el);
|
|
18
|
+
i.top = p.top, i.height = p.height;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
const G = "[data-midday-section]", K = "data-midday-section", J = "data-midday-target";
|
|
22
22
|
function Y(t) {
|
|
23
|
-
const i = document.querySelectorAll(G),
|
|
23
|
+
const i = document.querySelectorAll(G), p = [];
|
|
24
24
|
for (const s of i) {
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
27
|
-
const
|
|
28
|
-
|
|
25
|
+
const h = s.getAttribute(K);
|
|
26
|
+
if (!h) continue;
|
|
27
|
+
const w = s.getAttribute(J);
|
|
28
|
+
w && (!t || !w.split(" ").includes(t)) || p.push({ el: s, variant: h, top: 0, height: 0 });
|
|
29
29
|
}
|
|
30
|
-
return P(
|
|
30
|
+
return P(p), p;
|
|
31
31
|
}
|
|
32
32
|
function Q(t) {
|
|
33
|
-
let {
|
|
34
|
-
const { defaultName:
|
|
35
|
-
let
|
|
33
|
+
let { element: i, variants: p, sections: s } = t;
|
|
34
|
+
const { defaultName: h, onChange: w } = t;
|
|
35
|
+
let c = null, d = !1, x = "", r = null;
|
|
36
36
|
function L() {
|
|
37
|
-
|
|
37
|
+
y();
|
|
38
38
|
}
|
|
39
39
|
function S() {
|
|
40
|
-
P(s),
|
|
40
|
+
P(s), y();
|
|
41
41
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
42
|
+
function y() {
|
|
43
|
+
d || (d = !0, c = requestAnimationFrame(l));
|
|
44
44
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
45
|
+
function l() {
|
|
46
|
+
d = !1, v();
|
|
47
47
|
}
|
|
48
|
-
function
|
|
49
|
-
const
|
|
48
|
+
function v() {
|
|
49
|
+
const a = z(i), o = a.height;
|
|
50
50
|
if (o <= 0) return;
|
|
51
|
-
const W = window.scrollY, F =
|
|
52
|
-
let
|
|
51
|
+
const W = window.scrollY, F = a.top, q = F + o, R = [], _ = /* @__PURE__ */ new Map();
|
|
52
|
+
let H = o, $ = 0, j = 0;
|
|
53
53
|
const U = /* @__PURE__ */ new Map();
|
|
54
54
|
for (const e of s) {
|
|
55
|
-
const T = e.top - W,
|
|
55
|
+
const T = e.top - W, g = T + e.height, M = Math.max(F, T), u = Math.min(q, g), B = Math.max(0, u - M);
|
|
56
56
|
if (B > 0) {
|
|
57
57
|
j += B;
|
|
58
|
-
const
|
|
59
|
-
|
|
58
|
+
const b = M - F, E = q - u;
|
|
59
|
+
H = Math.min(H, b), $ = Math.max($, o - E);
|
|
60
60
|
const I = _.get(e.variant);
|
|
61
|
-
I ? (I.topInset = Math.min(I.topInset,
|
|
61
|
+
I ? (I.topInset = Math.min(I.topInset, b), I.bottomInset = Math.min(I.bottomInset, E)) : _.set(e.variant, { topInset: b, bottomInset: E });
|
|
62
62
|
}
|
|
63
63
|
let f = U.get(e.variant);
|
|
64
|
-
f || (f = [], U.set(e.variant, f)), f.push({ viewTop: T, viewBottom:
|
|
64
|
+
f || (f = [], U.set(e.variant, f)), f.push({ viewTop: T, viewBottom: g });
|
|
65
65
|
}
|
|
66
|
-
let
|
|
67
|
-
for (const e of
|
|
68
|
-
if (e.name ===
|
|
69
|
-
|
|
66
|
+
let C = null;
|
|
67
|
+
for (const e of p) {
|
|
68
|
+
if (e.name === h) {
|
|
69
|
+
C = e.wrapper;
|
|
70
70
|
continue;
|
|
71
71
|
}
|
|
72
72
|
const T = U.get(e.name);
|
|
@@ -74,118 +74,120 @@ function Q(t) {
|
|
|
74
74
|
e.wrapper.style.clipPath = "inset(0 0 100% 0)";
|
|
75
75
|
continue;
|
|
76
76
|
}
|
|
77
|
-
const
|
|
77
|
+
const g = e.wrapper.getBoundingClientRect(), M = g.height || o;
|
|
78
78
|
let u = M, B = M;
|
|
79
79
|
for (const f of T) {
|
|
80
|
-
const
|
|
81
|
-
|
|
80
|
+
const b = Math.max(g.top, f.viewTop), E = Math.min(g.bottom, f.viewBottom);
|
|
81
|
+
E <= b || (u = Math.min(u, b - g.top), B = Math.min(B, g.bottom - E));
|
|
82
82
|
}
|
|
83
83
|
if (u + B < M) {
|
|
84
84
|
e.wrapper.style.clipPath = `inset(${u}px 0 ${B}px 0)`;
|
|
85
|
-
const f = _.get(e.name),
|
|
86
|
-
R.push({ name: e.name, progress:
|
|
85
|
+
const f = _.get(e.name), b = f ? (o - f.topInset - f.bottomInset) / o : 0;
|
|
86
|
+
R.push({ name: e.name, progress: b });
|
|
87
87
|
} else
|
|
88
88
|
e.wrapper.style.clipPath = "inset(0 0 100% 0)";
|
|
89
89
|
}
|
|
90
|
-
if (
|
|
91
|
-
const e =
|
|
90
|
+
if (C) {
|
|
91
|
+
const e = C.getBoundingClientRect().height || o;
|
|
92
92
|
if (j >= o)
|
|
93
|
-
|
|
93
|
+
C.style.clipPath = "inset(0 0 100% 0)";
|
|
94
94
|
else if (j <= 0)
|
|
95
|
-
|
|
96
|
-
name:
|
|
95
|
+
C.style.clipPath = "inset(0)", R.unshift({
|
|
96
|
+
name: h,
|
|
97
97
|
progress: 1
|
|
98
98
|
});
|
|
99
99
|
else {
|
|
100
|
-
const T =
|
|
100
|
+
const T = H;
|
|
101
101
|
if (o - $ >= T) {
|
|
102
102
|
const u = e * ($ / o);
|
|
103
|
-
|
|
103
|
+
C.style.clipPath = `inset(${u}px 0 0 0)`;
|
|
104
104
|
} else {
|
|
105
|
-
const u = e * ((o -
|
|
106
|
-
|
|
105
|
+
const u = e * ((o - H) / o);
|
|
106
|
+
C.style.clipPath = `inset(0 0 ${u}px 0)`;
|
|
107
107
|
}
|
|
108
108
|
const M = o - j;
|
|
109
109
|
R.unshift({
|
|
110
|
-
name:
|
|
110
|
+
name: h,
|
|
111
111
|
progress: M / o
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
const D = R.map((e) => `${e.name}:${e.progress.toFixed(3)}`).join("|");
|
|
116
|
-
D !==
|
|
116
|
+
D !== x && (x = D, w == null || w(R));
|
|
117
117
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
P(s),
|
|
118
|
+
function m() {
|
|
119
|
+
r == null || r.disconnect(), r = new ResizeObserver(() => {
|
|
120
|
+
P(s), y();
|
|
121
121
|
});
|
|
122
|
-
for (const
|
|
123
|
-
|
|
122
|
+
for (const a of s)
|
|
123
|
+
r.observe(a.el);
|
|
124
124
|
}
|
|
125
125
|
function n() {
|
|
126
|
-
window.addEventListener("scroll", L, { passive: !0 }), window.addEventListener("resize", S, { passive: !0 }),
|
|
126
|
+
window.addEventListener("scroll", L, { passive: !0 }), window.addEventListener("resize", S, { passive: !0 }), m(), y();
|
|
127
127
|
}
|
|
128
|
-
function
|
|
129
|
-
P(s),
|
|
128
|
+
function A() {
|
|
129
|
+
P(s), y();
|
|
130
130
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
131
|
+
function V(a, o) {
|
|
132
|
+
p = a, s = o, P(s), m(), y();
|
|
133
133
|
}
|
|
134
|
-
function
|
|
135
|
-
|
|
134
|
+
function N() {
|
|
135
|
+
c !== null && cancelAnimationFrame(c), window.removeEventListener("scroll", L), window.removeEventListener("resize", S), r == null || r.disconnect(), r = null;
|
|
136
136
|
}
|
|
137
|
-
return n(), { recalculate:
|
|
137
|
+
return n(), { recalculate: A, update: V, destroy: N };
|
|
138
138
|
}
|
|
139
139
|
const X = "data-midday-variant", O = "default";
|
|
140
140
|
function Z(t, i = {}) {
|
|
141
|
-
const { onChange:
|
|
142
|
-
let
|
|
143
|
-
function
|
|
144
|
-
const
|
|
145
|
-
for (const
|
|
146
|
-
|
|
141
|
+
const { onChange: p } = i, s = i.name ?? (t.getAttribute("data-midday-element") || void 0), h = t.innerHTML, w = t.style.overflow;
|
|
142
|
+
let c = null, d = [];
|
|
143
|
+
function x() {
|
|
144
|
+
const l = Y(s), v = /* @__PURE__ */ new Set();
|
|
145
|
+
for (const a of l)
|
|
146
|
+
v.add(a.variant);
|
|
147
147
|
t.style.overflow = "visible";
|
|
148
|
-
const
|
|
148
|
+
const m = document.createDocumentFragment();
|
|
149
149
|
for (; t.firstChild; )
|
|
150
|
-
|
|
150
|
+
m.appendChild(t.firstChild);
|
|
151
151
|
const n = [];
|
|
152
|
-
for (const
|
|
153
|
-
n.push(
|
|
154
|
-
const
|
|
152
|
+
for (const a of v)
|
|
153
|
+
n.push(r(a, m, !0));
|
|
154
|
+
const A = document.createElement("div");
|
|
155
|
+
A.style.visibility = "hidden", A.style.pointerEvents = "none", A.setAttribute("aria-hidden", "true"), A.appendChild(m.cloneNode(!0)), t.appendChild(A);
|
|
156
|
+
const V = r(O, m, !1);
|
|
155
157
|
t.appendChild(V.wrapper);
|
|
156
|
-
const
|
|
157
|
-
for (const
|
|
158
|
-
t.appendChild(
|
|
159
|
-
return
|
|
158
|
+
const N = [V];
|
|
159
|
+
for (const a of n)
|
|
160
|
+
t.appendChild(a.wrapper), N.push(a);
|
|
161
|
+
return N;
|
|
160
162
|
}
|
|
161
|
-
function
|
|
163
|
+
function r(l, v, m) {
|
|
162
164
|
const n = document.createElement("div");
|
|
163
|
-
return n.setAttribute(X,
|
|
165
|
+
return n.setAttribute(X, l), n.style.position = "absolute", n.style.top = "0", n.style.left = "0", n.style.right = "0", n.style.bottom = "0", n.style.willChange = "clip-path", n.style.clipPath = "inset(0 0 100% 0)", m ? (n.setAttribute("aria-hidden", "true"), n.setAttribute("inert", ""), n.style.pointerEvents = "none", n.appendChild(v.cloneNode(!0))) : n.appendChild(v), { wrapper: n, name: l };
|
|
164
166
|
}
|
|
165
167
|
function L() {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
variants:
|
|
168
|
+
const l = Y(s);
|
|
169
|
+
d = x(), c = Q({
|
|
170
|
+
element: t,
|
|
171
|
+
variants: d,
|
|
170
172
|
defaultName: O,
|
|
171
|
-
sections:
|
|
172
|
-
onChange:
|
|
173
|
+
sections: l,
|
|
174
|
+
onChange: p
|
|
173
175
|
});
|
|
174
176
|
}
|
|
175
177
|
function S() {
|
|
176
|
-
for (const
|
|
177
|
-
|
|
178
|
-
t.innerHTML =
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
for (const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
return L(), { refresh: S, destroy:
|
|
178
|
+
for (const v of d)
|
|
179
|
+
v.wrapper.remove();
|
|
180
|
+
t.innerHTML = h;
|
|
181
|
+
const l = Y(s);
|
|
182
|
+
d = x(), c == null || c.update(d, l);
|
|
183
|
+
}
|
|
184
|
+
function y() {
|
|
185
|
+
c == null || c.destroy(), c = null;
|
|
186
|
+
for (const l of d)
|
|
187
|
+
l.wrapper.remove();
|
|
188
|
+
d = [], t.innerHTML = h, t.style.overflow = w;
|
|
189
|
+
}
|
|
190
|
+
return L(), { refresh: S, destroy: y };
|
|
189
191
|
}
|
|
190
192
|
export {
|
|
191
193
|
Z as a,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { MiddayOptions, MiddayHeadlessOptions, MiddayInstance, ActiveVariant } from './types';
|
|
2
2
|
/**
|
|
3
|
-
* Auto mode — Initialize midday.js on a fixed
|
|
4
|
-
* Automatically clones
|
|
3
|
+
* Auto mode — Initialize midday.js on a fixed element.
|
|
4
|
+
* Automatically clones element content for each variant and manages the DOM.
|
|
5
5
|
*
|
|
6
|
-
* @param
|
|
6
|
+
* @param element - The fixed/sticky element marked with data-midday-element
|
|
7
7
|
* @param options - Optional configuration
|
|
8
8
|
* @returns Instance with refresh() and destroy() methods
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```js
|
|
12
|
-
* const instance = midday(document.querySelector('[data-midday]'));
|
|
12
|
+
* const instance = midday(document.querySelector('[data-midday-element]'));
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
|
-
export declare function midday(
|
|
15
|
+
export declare function midday(element: HTMLElement, options?: MiddayOptions): MiddayInstance;
|
|
16
16
|
/**
|
|
17
17
|
* Headless mode — Bring your own variant elements.
|
|
18
18
|
* Manages only clip-paths on pre-rendered elements. No DOM cloning.
|
package/dist/midday.mjs
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
import { s as i, c as y, a as p } from "./core.mjs";
|
|
2
|
-
function
|
|
2
|
+
function v(a) {
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
element: e,
|
|
5
5
|
variants: c,
|
|
6
6
|
defaultVariant: o = "default",
|
|
7
|
-
name:
|
|
8
|
-
onChange:
|
|
7
|
+
name: s,
|
|
8
|
+
onChange: u
|
|
9
9
|
} = a;
|
|
10
10
|
let t = null;
|
|
11
|
-
function
|
|
11
|
+
function r() {
|
|
12
12
|
return Object.entries(c).map(([n, m]) => ({
|
|
13
13
|
name: n,
|
|
14
14
|
wrapper: m
|
|
15
15
|
}));
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
const n = i(
|
|
17
|
+
function d() {
|
|
18
|
+
const n = i(s);
|
|
19
19
|
t = y({
|
|
20
|
-
|
|
21
|
-
variants:
|
|
20
|
+
element: e,
|
|
21
|
+
variants: r(),
|
|
22
22
|
defaultName: o,
|
|
23
23
|
sections: n,
|
|
24
|
-
onChange:
|
|
24
|
+
onChange: u
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
function f() {
|
|
28
|
-
const n = i(
|
|
29
|
-
t == null || t.update(
|
|
28
|
+
const n = i(s);
|
|
29
|
+
t == null || t.update(r(), n);
|
|
30
30
|
}
|
|
31
31
|
function l() {
|
|
32
32
|
t == null || t.destroy(), t = null;
|
|
33
33
|
}
|
|
34
|
-
return
|
|
34
|
+
return d(), { refresh: f, destroy: l };
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function h(a, e) {
|
|
37
37
|
return p(a, e);
|
|
38
38
|
}
|
|
39
39
|
function H(a) {
|
|
40
|
-
return
|
|
40
|
+
return v(a);
|
|
41
41
|
}
|
|
42
42
|
export {
|
|
43
|
-
|
|
43
|
+
h as midday,
|
|
44
44
|
H as middayHeadless
|
|
45
45
|
};
|
package/dist/midday.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(C,I){typeof exports=="object"&&typeof module<"u"?I(exports):typeof define=="function"&&define.amd?define(["exports"],I):(C=typeof globalThis<"u"?globalThis:C||self,I(C.midday={}))})(this,(function(C){"use strict";function I(t){const o=t.getBoundingClientRect();return{top:o.top+window.scrollY,height:o.height}}function G(t){const o=t.getBoundingClientRect();return{top:o.top,height:o.height}}function V(t){for(const o of t){const l=I(o.el);o.top=l.top,o.height=l.height}}const K="[data-midday-section]",J="data-midday-section",Q="data-midday-target";function
|
|
1
|
+
(function(C,I){typeof exports=="object"&&typeof module<"u"?I(exports):typeof define=="function"&&define.amd?define(["exports"],I):(C=typeof globalThis<"u"?globalThis:C||self,I(C.midday={}))})(this,(function(C){"use strict";function I(t){const o=t.getBoundingClientRect();return{top:o.top+window.scrollY,height:o.height}}function G(t){const o=t.getBoundingClientRect();return{top:o.top,height:o.height}}function V(t){for(const o of t){const l=I(o.el);o.top=l.top,o.height=l.height}}const K="[data-midday-section]",J="data-midday-section",Q="data-midday-target";function N(t){const o=document.querySelectorAll(K),l=[];for(const s of o){const u=s.getAttribute(J);if(!u)continue;const m=s.getAttribute(Q);m&&(!t||!m.split(" ").includes(t))||l.push({el:s,variant:u,top:0,height:0})}return V(l),l}function D(t){let{element:o,variants:l,sections:s}=t;const{defaultName:u,onChange:m}=t;let n=null,c=!1,B="",r=null;function P(){g()}function v(){V(s),g()}function g(){c||(c=!0,n=requestAnimationFrame(p))}function p(){c=!1,w()}function w(){const d=G(o),a=d.height;if(a<=0)return;const ot=window.scrollY,U=d.top,k=U+a,H=[],Y=new Map;let F=a,_=0,O=0;const q=new Map;for(const e of s){const M=e.top-ot,T=M+e.height,b=Math.max(U,M),f=Math.min(k,T),R=Math.max(0,f-b);if(R>0){O+=R;const A=b-U,S=k-f;F=Math.min(F,A),_=Math.max(_,a-S);const L=Y.get(e.variant);L?(L.topInset=Math.min(L.topInset,A),L.bottomInset=Math.min(L.bottomInset,S)):Y.set(e.variant,{topInset:A,bottomInset:S})}let h=q.get(e.variant);h||(h=[],q.set(e.variant,h)),h.push({viewTop:M,viewBottom:T})}let x=null;for(const e of l){if(e.name===u){x=e.wrapper;continue}const M=q.get(e.name);if(!M){e.wrapper.style.clipPath="inset(0 0 100% 0)";continue}const T=e.wrapper.getBoundingClientRect(),b=T.height||a;let f=b,R=b;for(const h of M){const A=Math.max(T.top,h.viewTop),S=Math.min(T.bottom,h.viewBottom);S<=A||(f=Math.min(f,A-T.top),R=Math.min(R,T.bottom-S))}if(f+R<b){e.wrapper.style.clipPath=`inset(${f}px 0 ${R}px 0)`;const h=Y.get(e.name),A=h?(a-h.topInset-h.bottomInset)/a:0;H.push({name:e.name,progress:A})}else e.wrapper.style.clipPath="inset(0 0 100% 0)"}if(x){const e=x.getBoundingClientRect().height||a;if(O>=a)x.style.clipPath="inset(0 0 100% 0)";else if(O<=0)x.style.clipPath="inset(0)",H.unshift({name:u,progress:1});else{const M=F;if(a-_>=M){const f=e*(_/a);x.style.clipPath=`inset(${f}px 0 0 0)`}else{const f=e*((a-F)/a);x.style.clipPath=`inset(0 0 ${f}px 0)`}const b=a-O;H.unshift({name:u,progress:b/a})}}const z=H.map(e=>`${e.name}:${e.progress.toFixed(3)}`).join("|");z!==B&&(B=z,m==null||m(H))}function y(){r==null||r.disconnect(),r=new ResizeObserver(()=>{V(s),g()});for(const d of s)r.observe(d.el)}function i(){window.addEventListener("scroll",P,{passive:!0}),window.addEventListener("resize",v,{passive:!0}),y(),g()}function E(){V(s),g()}function j(d,a){l=d,s=a,V(s),y(),g()}function $(){n!==null&&cancelAnimationFrame(n),window.removeEventListener("scroll",P),window.removeEventListener("resize",v),r==null||r.disconnect(),r=null}return i(),{recalculate:E,update:j,destroy:$}}const X="data-midday-variant",W="default";function Z(t,o={}){const{onChange:l}=o,s=o.name??(t.getAttribute("data-midday-element")||void 0),u=t.innerHTML,m=t.style.overflow;let n=null,c=[];function B(){const p=N(s),w=new Set;for(const d of p)w.add(d.variant);t.style.overflow="visible";const y=document.createDocumentFragment();for(;t.firstChild;)y.appendChild(t.firstChild);const i=[];for(const d of w)i.push(r(d,y,!0));const E=document.createElement("div");E.style.visibility="hidden",E.style.pointerEvents="none",E.setAttribute("aria-hidden","true"),E.appendChild(y.cloneNode(!0)),t.appendChild(E);const j=r(W,y,!1);t.appendChild(j.wrapper);const $=[j];for(const d of i)t.appendChild(d.wrapper),$.push(d);return $}function r(p,w,y){const i=document.createElement("div");return i.setAttribute(X,p),i.style.position="absolute",i.style.top="0",i.style.left="0",i.style.right="0",i.style.bottom="0",i.style.willChange="clip-path",i.style.clipPath="inset(0 0 100% 0)",y?(i.setAttribute("aria-hidden","true"),i.setAttribute("inert",""),i.style.pointerEvents="none",i.appendChild(w.cloneNode(!0))):i.appendChild(w),{wrapper:i,name:p}}function P(){const p=N(s);c=B(),n=D({element:t,variants:c,defaultName:W,sections:p,onChange:l})}function v(){for(const w of c)w.wrapper.remove();t.innerHTML=u;const p=N(s);c=B(),n==null||n.update(c,p)}function g(){n==null||n.destroy(),n=null;for(const p of c)p.wrapper.remove();c=[],t.innerHTML=u,t.style.overflow=m}return P(),{refresh:v,destroy:g}}function tt(t){const{element:o,variants:l,defaultVariant:s="default",name:u,onChange:m}=t;let n=null;function c(){return Object.entries(l).map(([v,g])=>({name:v,wrapper:g}))}function B(){const v=N(u);n=D({element:o,variants:c(),defaultName:s,sections:v,onChange:m})}function r(){const v=N(u);n==null||n.update(c(),v)}function P(){n==null||n.destroy(),n=null}return B(),{refresh:r,destroy:P}}function et(t,o){return Z(t,o)}function nt(t){return tt(t)}C.midday=et,C.middayHeadless=nt,Object.defineProperty(C,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/react.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ import type { MiddayOptions, MiddayInstance } from './types';
|
|
|
4
4
|
* Initializes on mount, destroys on unmount.
|
|
5
5
|
* Cloning happens client-side — safe for SSR.
|
|
6
6
|
*/
|
|
7
|
-
export declare function useMidday(
|
|
7
|
+
export declare function useMidday(elementRef: React.RefObject<HTMLElement | null>, options?: MiddayOptions): React.RefObject<MiddayInstance | null>;
|
package/dist/solid.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { MiddayOptions, MiddayInstance } from './types';
|
|
|
4
4
|
* Initializes on mount, cleans up on disposal.
|
|
5
5
|
* Cloning happens client-side — safe for SSR.
|
|
6
6
|
*/
|
|
7
|
-
export declare function createMidday(
|
|
7
|
+
export declare function createMidday(elementAccessor: () => HTMLElement | null, options?: MiddayOptions): () => MiddayInstance | null;
|
|
8
8
|
/**
|
|
9
9
|
* Solid directive for midday.js (auto mode).
|
|
10
10
|
* Usage: <header use:midday> or <header use:midday={{ onChange }}>
|
package/dist/solid.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { onMount as d, onCleanup as e } from "solid-js";
|
|
2
2
|
import { a } from "./core.mjs";
|
|
3
|
-
function
|
|
3
|
+
function l(n, o) {
|
|
4
4
|
let t = null;
|
|
5
5
|
return d(() => {
|
|
6
6
|
const r = n();
|
|
@@ -9,13 +9,13 @@ function c(n, o) {
|
|
|
9
9
|
t == null || t.destroy(), t = null;
|
|
10
10
|
}), () => t;
|
|
11
11
|
}
|
|
12
|
-
function
|
|
12
|
+
function c(n, o) {
|
|
13
13
|
const t = a(n, o());
|
|
14
14
|
e(() => {
|
|
15
15
|
t.destroy();
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
export {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
l as createMidday,
|
|
20
|
+
c as midday
|
|
21
21
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface ActiveVariant {
|
|
2
2
|
/** The variant name (from data-midday-section value) */
|
|
3
3
|
name: string;
|
|
4
|
-
/** How much of the
|
|
4
|
+
/** How much of the element this variant covers (0 to 1) */
|
|
5
5
|
progress: number;
|
|
6
6
|
}
|
|
7
7
|
export interface MiddayInstance {
|
|
@@ -21,14 +21,14 @@ export interface VariantState {
|
|
|
21
21
|
name: string;
|
|
22
22
|
}
|
|
23
23
|
export interface MiddayOptions {
|
|
24
|
-
/** Instance name for multi-instance scoping. Sections with a matching data-midday-target will be claimed by this instance. Defaults to the
|
|
24
|
+
/** Instance name for multi-instance scoping. Sections with a matching data-midday-target will be claimed by this instance. Defaults to the element's data-midday-element attribute value. */
|
|
25
25
|
name?: string;
|
|
26
26
|
/** Called when the set of visible variants changes */
|
|
27
27
|
onChange?: ((variants: ActiveVariant[]) => void) | null;
|
|
28
28
|
}
|
|
29
29
|
export interface MiddayHeadlessOptions {
|
|
30
|
-
/** The fixed/sticky
|
|
31
|
-
|
|
30
|
+
/** The fixed/sticky element (used for position calculations) */
|
|
31
|
+
element: HTMLElement;
|
|
32
32
|
/** Map of variant name to wrapper element. The plugin manages clip-paths on these. */
|
|
33
33
|
variants: Record<string, HTMLElement>;
|
|
34
34
|
/** Which key in `variants` is the default (shown where no section overlaps). Defaults to 'default'. */
|
|
@@ -39,7 +39,7 @@ export interface MiddayHeadlessOptions {
|
|
|
39
39
|
onChange?: ((variants: ActiveVariant[]) => void) | null;
|
|
40
40
|
}
|
|
41
41
|
export interface EngineConfig {
|
|
42
|
-
|
|
42
|
+
element: HTMLElement;
|
|
43
43
|
variants: VariantState[];
|
|
44
44
|
defaultName: string;
|
|
45
45
|
sections: SectionData[];
|
package/dist/utils.d.ts
CHANGED
|
@@ -8,10 +8,10 @@ export declare function getSectionBounds(el: Element): {
|
|
|
8
8
|
height: number;
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
|
-
* Get the
|
|
11
|
+
* Get the element's current viewport-relative bounding rect.
|
|
12
12
|
* This accounts for CSS transforms, sticky offsets, etc.
|
|
13
13
|
*/
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function getElementBounds(el: HTMLElement): {
|
|
15
15
|
top: number;
|
|
16
16
|
height: number;
|
|
17
17
|
};
|
package/dist/vue.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { MiddayOptions, MiddayInstance } from './types';
|
|
|
5
5
|
* Initializes on mount, destroys on unmount.
|
|
6
6
|
* Cloning happens client-side — safe for SSR.
|
|
7
7
|
*/
|
|
8
|
-
export declare function useMidday(
|
|
8
|
+
export declare function useMidday(elementRef: Ref<HTMLElement | null>, options?: MiddayOptions): Ref<MiddayInstance | null>;
|
|
9
9
|
/**
|
|
10
10
|
* Vue custom directive for midday.js (auto mode).
|
|
11
11
|
* Usage: <header v-midday> or <header v-midday="{ onChange }">
|
package/dist/vue.mjs
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { shallowRef as o, onMounted as u, onUnmounted as s } from "vue";
|
|
2
2
|
import { a as d } from "./core.mjs";
|
|
3
|
-
function
|
|
4
|
-
const
|
|
3
|
+
function c(n, a) {
|
|
4
|
+
const e = o(null);
|
|
5
5
|
return u(() => {
|
|
6
|
-
n.value && (
|
|
6
|
+
n.value && (e.value = d(n.value, a));
|
|
7
7
|
}), s(() => {
|
|
8
|
-
var
|
|
9
|
-
(
|
|
10
|
-
}),
|
|
8
|
+
var t;
|
|
9
|
+
(t = e.value) == null || t.destroy(), e.value = null;
|
|
10
|
+
}), e;
|
|
11
11
|
}
|
|
12
|
-
const
|
|
13
|
-
mounted(n,
|
|
14
|
-
const
|
|
15
|
-
n.__middayInstance =
|
|
12
|
+
const l = {
|
|
13
|
+
mounted(n, a) {
|
|
14
|
+
const e = d(n, a.value);
|
|
15
|
+
n.__middayInstance = e;
|
|
16
16
|
},
|
|
17
17
|
unmounted(n) {
|
|
18
|
-
var
|
|
19
|
-
(
|
|
18
|
+
var a;
|
|
19
|
+
(a = n.__middayInstance) == null || a.destroy(), delete n.__middayInstance;
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
export {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
c as useMidday,
|
|
24
|
+
l as vMidday
|
|
25
25
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcwiest/midday.js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A modern vanilla JS plugin for fixed headers that change style as you scroll through sections. Zero dependencies. The spiritual successor to midnight.js.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|