@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 CHANGED
@@ -5,7 +5,7 @@
5
5
  [![CI](https://github.com/marcwiest/midday.js/actions/workflows/ci.yml/badge.svg)](https://github.com/marcwiest/midday.js/actions/workflows/ci.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- A modern, zero-dependency vanilla JS plugin for fixed headers that change style as you scroll through page sections. The spiritual successor to [midnight.js](https://github.com/Aerolab/midnight.js).
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. Write one header, mark your sections
39
+ ### 1. Mark your element, add your sections
40
40
 
41
- You write a single header. Each section declares which header style it wants via `data-midday-section`:
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 header 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:
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> ← your element (position: fixed)
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 header transitions smoothly from one section to another.
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(header, options?)` — Auto mode
129
+ ### `midday(element, options?)` — Auto mode
130
130
 
131
- Clones your header content once per variant and manages everything. Sections are discovered automatically via `data-midday-section` attributes.
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
- header: document.querySelector('header'),
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(); // Re-scan sections and recalculate (call after DOM changes)
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(header, {
191
+ midday(element, {
175
192
  onChange: (variants) => {
176
193
  // variants: Array<{ name: string, progress: number }>
177
- // progress: 0–1, how much of the header this variant covers
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 header element, and cloning happens client-side on mount. This means your server-rendered HTML always contains just one header, keeping SEO clean (see [SSR & SEO](#ssr--seo) below).
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 headerRef = useRef(null);
198
- useMidday(headerRef);
214
+ const elementRef = useRef(null);
215
+ useMidday(elementRef);
199
216
 
200
217
  return (
201
- <header ref={headerRef} style={{ position: 'fixed', top: 0, left: 0, right: 0 }}>
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 headerRef = ref(null);
218
- useMidday(headerRef);
234
+ const elementRef = ref(null);
235
+ useMidday(elementRef);
219
236
  </script>
220
237
 
221
238
  <template>
222
- <header ref="headerRef">
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 headerEl;
263
- createMidday(() => headerEl);
279
+ let el;
280
+ createMidday(() => el);
264
281
 
265
282
  return (
266
- <header ref={headerEl} style={{ position: 'fixed', top: '0', left: '0', right: '0' }}>
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(headerRef, { onChange: (v) => console.log(v) });
310
+ useMidday(elementRef, { onChange: (v) => console.log(v) });
294
311
 
295
312
  // Vue (composable)
296
- useMidday(headerRef, { onChange: (v) => console.log(v) });
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(() => headerEl, { onChange: (v) => console.log(v) });
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 header -->
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 header content client-side on mount. The server-rendered HTML always contains a single, clean header element — no duplicate navigation links, no hidden clones. Search engine crawlers see exactly one header with one set of nav links.
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 header content remains the accessible version.
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 headerRef = useRef(null);
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 || !headerRef.current) return;
379
+ if (!mounted || !elementRef.current) return;
363
380
  const instance = middayHeadless({
364
- header: headerRef.current,
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={headerRef}>
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 header elements as sections scroll past.
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 header content once per unique variant found in `data-midday-section` attributes. Each clone is wrapped in an absolutely-positioned container inside the header element.
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 header's viewport position and by how many pixels.
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 header. Every variant — including the default — is clipped to only its own region. Nothing is used as a backdrop, so transparent backgrounds work fine.
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 header element should be `position: fixed` or `position: sticky`
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 header and setting `aria-hidden`/`inert` on non-default variants
403
- - In headless mode, variant elements can have different heights than the header. The clip-path will track section boundaries exactly, with extra height revealing only when the section extends past the header edge
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(header: HTMLElement, options?: MiddayOptions): MiddayInstance;
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 l = k(i.el);
18
- i.top = l.top, i.height = l.height;
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), l = [];
23
+ const i = document.querySelectorAll(G), p = [];
24
24
  for (const s of i) {
25
- const d = s.getAttribute(K);
26
- if (!d) continue;
27
- const v = s.getAttribute(J);
28
- v && (!t || !v.split(" ").includes(t)) || l.push({ el: s, variant: d, top: 0, height: 0 });
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(l), l;
30
+ return P(p), p;
31
31
  }
32
32
  function Q(t) {
33
- let { header: i, variants: l, sections: s } = t;
34
- const { defaultName: d, onChange: v } = t;
35
- let a = null, p = !1, E = "", c = null;
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
- g();
37
+ y();
38
38
  }
39
39
  function S() {
40
- P(s), g();
40
+ P(s), y();
41
41
  }
42
- function g() {
43
- p || (p = !0, a = requestAnimationFrame(r));
42
+ function y() {
43
+ d || (d = !0, c = requestAnimationFrame(l));
44
44
  }
45
- function r() {
46
- p = !1, h();
45
+ function l() {
46
+ d = !1, v();
47
47
  }
48
- function h() {
49
- const C = z(i), o = C.height;
48
+ function v() {
49
+ const a = z(i), o = a.height;
50
50
  if (o <= 0) return;
51
- const W = window.scrollY, F = C.top, q = F + o, R = [], _ = /* @__PURE__ */ new Map();
52
- let N = o, $ = 0, j = 0;
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, m = T + e.height, M = Math.max(F, T), u = Math.min(q, m), B = Math.max(0, u - M);
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 A = M - F, x = q - u;
59
- N = Math.min(N, A), $ = Math.max($, o - x);
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, A), I.bottomInset = Math.min(I.bottomInset, x)) : _.set(e.variant, { topInset: A, bottomInset: x });
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: m });
64
+ f || (f = [], U.set(e.variant, f)), f.push({ viewTop: T, viewBottom: g });
65
65
  }
66
- let b = null;
67
- for (const e of l) {
68
- if (e.name === d) {
69
- b = e.wrapper;
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 m = e.wrapper.getBoundingClientRect(), M = m.height || o;
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 A = Math.max(m.top, f.viewTop), x = Math.min(m.bottom, f.viewBottom);
81
- x <= A || (u = Math.min(u, A - m.top), B = Math.min(B, m.bottom - x));
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), A = f ? (o - f.topInset - f.bottomInset) / o : 0;
86
- R.push({ name: e.name, progress: A });
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 (b) {
91
- const e = b.getBoundingClientRect().height || o;
90
+ if (C) {
91
+ const e = C.getBoundingClientRect().height || o;
92
92
  if (j >= o)
93
- b.style.clipPath = "inset(0 0 100% 0)";
93
+ C.style.clipPath = "inset(0 0 100% 0)";
94
94
  else if (j <= 0)
95
- b.style.clipPath = "inset(0)", R.unshift({
96
- name: d,
95
+ C.style.clipPath = "inset(0)", R.unshift({
96
+ name: h,
97
97
  progress: 1
98
98
  });
99
99
  else {
100
- const T = N;
100
+ const T = H;
101
101
  if (o - $ >= T) {
102
102
  const u = e * ($ / o);
103
- b.style.clipPath = `inset(${u}px 0 0 0)`;
103
+ C.style.clipPath = `inset(${u}px 0 0 0)`;
104
104
  } else {
105
- const u = e * ((o - N) / o);
106
- b.style.clipPath = `inset(0 0 ${u}px 0)`;
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: d,
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 !== E && (E = D, v == null || v(R));
116
+ D !== x && (x = D, w == null || w(R));
117
117
  }
118
- function w() {
119
- c == null || c.disconnect(), c = new ResizeObserver(() => {
120
- P(s), g();
118
+ function m() {
119
+ r == null || r.disconnect(), r = new ResizeObserver(() => {
120
+ P(s), y();
121
121
  });
122
- for (const C of s)
123
- c.observe(C.el);
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 }), w(), g();
126
+ window.addEventListener("scroll", L, { passive: !0 }), window.addEventListener("resize", S, { passive: !0 }), m(), y();
127
127
  }
128
- function V() {
129
- P(s), g();
128
+ function A() {
129
+ P(s), y();
130
130
  }
131
- function H(C, o) {
132
- l = C, s = o, P(s), w(), g();
131
+ function V(a, o) {
132
+ p = a, s = o, P(s), m(), y();
133
133
  }
134
- function y() {
135
- a !== null && cancelAnimationFrame(a), window.removeEventListener("scroll", L), window.removeEventListener("resize", S), c == null || c.disconnect(), c = null;
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: V, update: H, destroy: y };
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: l } = i, s = i.name ?? (t.getAttribute("data-midday") || void 0), d = t.innerHTML, v = t.style.overflow;
142
- let a = null, p = [];
143
- function E() {
144
- const r = Y(s), h = /* @__PURE__ */ new Set();
145
- for (const y of r)
146
- h.add(y.variant);
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 w = document.createDocumentFragment();
148
+ const m = document.createDocumentFragment();
149
149
  for (; t.firstChild; )
150
- w.appendChild(t.firstChild);
150
+ m.appendChild(t.firstChild);
151
151
  const n = [];
152
- for (const y of h)
153
- n.push(c(y, w, !0));
154
- const V = c(O, w, !1);
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 H = [V];
157
- for (const y of n)
158
- t.appendChild(y.wrapper), H.push(y);
159
- return H;
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 c(r, h, w) {
163
+ function r(l, v, m) {
162
164
  const n = document.createElement("div");
163
- return n.setAttribute(X, r), 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)", w ? (n.setAttribute("aria-hidden", "true"), n.setAttribute("inert", ""), n.style.pointerEvents = "none", n.appendChild(h.cloneNode(!0))) : n.appendChild(h), { wrapper: n, name: r };
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 r = Y(s);
167
- p = E(), a = Q({
168
- header: t,
169
- variants: p,
168
+ const l = Y(s);
169
+ d = x(), c = Q({
170
+ element: t,
171
+ variants: d,
170
172
  defaultName: O,
171
- sections: r,
172
- onChange: l
173
+ sections: l,
174
+ onChange: p
173
175
  });
174
176
  }
175
177
  function S() {
176
- for (const h of p)
177
- h.wrapper.remove();
178
- t.innerHTML = d;
179
- const r = Y(s);
180
- p = E(), a == null || a.update(p, r);
181
- }
182
- function g() {
183
- a == null || a.destroy(), a = null;
184
- for (const r of p)
185
- r.wrapper.remove();
186
- p = [], t.innerHTML = d, t.style.overflow = v;
187
- }
188
- return L(), { refresh: S, destroy: g };
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 header element.
4
- * Automatically clones header content for each variant and manages the DOM.
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 header - The fixed/sticky header element
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(header: HTMLElement, options?: MiddayOptions): MiddayInstance;
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 h(a) {
2
+ function v(a) {
3
3
  const {
4
- header: e,
4
+ element: e,
5
5
  variants: c,
6
6
  defaultVariant: o = "default",
7
- name: r,
8
- onChange: d
7
+ name: s,
8
+ onChange: u
9
9
  } = a;
10
10
  let t = null;
11
- function s() {
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 u() {
18
- const n = i(r);
17
+ function d() {
18
+ const n = i(s);
19
19
  t = y({
20
- header: e,
21
- variants: s(),
20
+ element: e,
21
+ variants: r(),
22
22
  defaultName: o,
23
23
  sections: n,
24
- onChange: d
24
+ onChange: u
25
25
  });
26
26
  }
27
27
  function f() {
28
- const n = i(r);
29
- t == null || t.update(s(), n);
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 u(), { refresh: f, destroy: l };
34
+ return d(), { refresh: f, destroy: l };
35
35
  }
36
- function M(a, e) {
36
+ function h(a, e) {
37
37
  return p(a, e);
38
38
  }
39
39
  function H(a) {
40
- return h(a);
40
+ return v(a);
41
41
  }
42
42
  export {
43
- M as midday,
43
+ h as midday,
44
44
  H as middayHeadless
45
45
  };
@@ -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 H(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{header:o,variants:l,sections:s}=t;const{defaultName:u,onChange:m}=t;let n=null,c=!1,B="",r=null;function R(){v()}function h(){V(s),v()}function v(){c||(c=!0,n=requestAnimationFrame(d))}function d(){c=!1,g()}function g(){const P=G(o),a=P.height;if(a<=0)return;const ot=window.scrollY,U=P.top,k=U+a,L=[],Y=new Map;let F=a,_=0,O=0;const q=new Map;for(const e of s){const M=e.top-ot,w=M+e.height,b=Math.max(U,M),p=Math.min(k,w),E=Math.max(0,p-b);if(E>0){O+=E;const A=b-U,S=k-p;F=Math.min(F,A),_=Math.max(_,a-S);const N=Y.get(e.variant);N?(N.topInset=Math.min(N.topInset,A),N.bottomInset=Math.min(N.bottomInset,S)):Y.set(e.variant,{topInset:A,bottomInset:S})}let f=q.get(e.variant);f||(f=[],q.set(e.variant,f)),f.push({viewTop:M,viewBottom:w})}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 w=e.wrapper.getBoundingClientRect(),b=w.height||a;let p=b,E=b;for(const f of M){const A=Math.max(w.top,f.viewTop),S=Math.min(w.bottom,f.viewBottom);S<=A||(p=Math.min(p,A-w.top),E=Math.min(E,w.bottom-S))}if(p+E<b){e.wrapper.style.clipPath=`inset(${p}px 0 ${E}px 0)`;const f=Y.get(e.name),A=f?(a-f.topInset-f.bottomInset)/a:0;L.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)",L.unshift({name:u,progress:1});else{const M=F;if(a-_>=M){const p=e*(_/a);x.style.clipPath=`inset(${p}px 0 0 0)`}else{const p=e*((a-F)/a);x.style.clipPath=`inset(0 0 ${p}px 0)`}const b=a-O;L.unshift({name:u,progress:b/a})}}const z=L.map(e=>`${e.name}:${e.progress.toFixed(3)}`).join("|");z!==B&&(B=z,m==null||m(L))}function y(){r==null||r.disconnect(),r=new ResizeObserver(()=>{V(s),v()});for(const P of s)r.observe(P.el)}function i(){window.addEventListener("scroll",R,{passive:!0}),window.addEventListener("resize",h,{passive:!0}),y(),v()}function j(){V(s),v()}function $(P,a){l=P,s=a,V(s),y(),v()}function T(){n!==null&&cancelAnimationFrame(n),window.removeEventListener("scroll",R),window.removeEventListener("resize",h),r==null||r.disconnect(),r=null}return i(),{recalculate:j,update:$,destroy:T}}const X="data-midday-variant",W="default";function Z(t,o={}){const{onChange:l}=o,s=o.name??(t.getAttribute("data-midday")||void 0),u=t.innerHTML,m=t.style.overflow;let n=null,c=[];function B(){const d=H(s),g=new Set;for(const T of d)g.add(T.variant);t.style.overflow="visible";const y=document.createDocumentFragment();for(;t.firstChild;)y.appendChild(t.firstChild);const i=[];for(const T of g)i.push(r(T,y,!0));const j=r(W,y,!1);t.appendChild(j.wrapper);const $=[j];for(const T of i)t.appendChild(T.wrapper),$.push(T);return $}function r(d,g,y){const i=document.createElement("div");return i.setAttribute(X,d),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(g.cloneNode(!0))):i.appendChild(g),{wrapper:i,name:d}}function R(){const d=H(s);c=B(),n=D({header:t,variants:c,defaultName:W,sections:d,onChange:l})}function h(){for(const g of c)g.wrapper.remove();t.innerHTML=u;const d=H(s);c=B(),n==null||n.update(c,d)}function v(){n==null||n.destroy(),n=null;for(const d of c)d.wrapper.remove();c=[],t.innerHTML=u,t.style.overflow=m}return R(),{refresh:h,destroy:v}}function tt(t){const{header:o,variants:l,defaultVariant:s="default",name:u,onChange:m}=t;let n=null;function c(){return Object.entries(l).map(([h,v])=>({name:h,wrapper:v}))}function B(){const h=H(u);n=D({header:o,variants:c(),defaultName:s,sections:h,onChange:m})}function r(){const h=H(u);n==null||n.update(c(),h)}function R(){n==null||n.destroy(),n=null}return B(),{refresh:r,destroy:R}}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"})}));
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(headerRef: React.RefObject<HTMLElement | null>, options?: MiddayOptions): React.RefObject<MiddayInstance | null>;
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(headerAccessor: () => HTMLElement | null, options?: MiddayOptions): () => MiddayInstance | null;
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 c(n, o) {
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 l(n, o) {
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
- c as createMidday,
20
- l as midday
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 header this variant covers (0 to 1) */
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 header's data-midday attribute value. */
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 header element (used for position calculations) */
31
- header: HTMLElement;
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
- header: HTMLElement;
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 header's current viewport-relative bounding rect.
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 getHeaderBounds(el: HTMLElement): {
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(headerRef: Ref<HTMLElement | null>, options?: MiddayOptions): Ref<MiddayInstance | null>;
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 r(n, e) {
4
- const t = o(null);
3
+ function c(n, a) {
4
+ const e = o(null);
5
5
  return u(() => {
6
- n.value && (t.value = d(n.value, e));
6
+ n.value && (e.value = d(n.value, a));
7
7
  }), s(() => {
8
- var a;
9
- (a = t.value) == null || a.destroy(), t.value = null;
10
- }), t;
8
+ var t;
9
+ (t = e.value) == null || t.destroy(), e.value = null;
10
+ }), e;
11
11
  }
12
- const c = {
13
- mounted(n, e) {
14
- const t = d(n, e.value);
15
- n.__middayInstance = t;
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 e;
19
- (e = n.__middayInstance) == null || e.destroy(), delete n.__middayInstance;
18
+ var a;
19
+ (a = n.__middayInstance) == null || a.destroy(), delete n.__middayInstance;
20
20
  }
21
21
  };
22
22
  export {
23
- r as useMidday,
24
- c as vMidday
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.1.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"