meno-interactions 0.1.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 ADDED
@@ -0,0 +1,611 @@
1
+ # Meno Interactions
2
+
3
+ Attribute-driven JavaScript interactions for Meno projects and static no-code sites.
4
+
5
+ Meno already supports component JavaScript with vanilla DOM code scoped to the component root, and it supports interactive styles through state classes such as `.is-open` or `.is-scrolled`. This package gives no-code builders a reusable script they can add once, then control behavior with custom attributes in the Meno Attributes panel.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install meno-interactions
11
+ ```
12
+
13
+ Or use the browser files from `dist/` in a Meno project:
14
+
15
+ ```json
16
+ {
17
+ "meta": {
18
+ "libraries": {
19
+ "js": [{ "url": "/libraries/meno-interactions.umd.js", "mode": "defer" }],
20
+ "css": [{ "url": "/libraries/meno-interactions.css" }]
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## CDN Setup
27
+
28
+ After this package is published to npm, you can load it from a CDN with one CSS file and one deferred script.
29
+
30
+ ### Option 1: jsDelivr
31
+
32
+ ```html
33
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/meno-interactions/dist/meno-interactions.css">
34
+ <script defer src="https://cdn.jsdelivr.net/npm/meno-interactions/dist/meno-interactions.umd.js"></script>
35
+ ```
36
+
37
+ ### Option 2: unpkg
38
+
39
+ ```html
40
+ <link rel="stylesheet" href="https://unpkg.com/meno-interactions/dist/meno-interactions.css">
41
+ <script defer src="https://unpkg.com/meno-interactions/dist/meno-interactions.umd.js"></script>
42
+ ```
43
+
44
+ ### Option 3: Version-pin the CDN
45
+
46
+ Pinning a version prevents future package updates from changing an already-launched site.
47
+
48
+ ```html
49
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/meno-interactions@0.1.0/dist/meno-interactions.css">
50
+ <script defer src="https://cdn.jsdelivr.net/npm/meno-interactions@0.1.0/dist/meno-interactions.umd.js"></script>
51
+ ```
52
+
53
+ ### Add CDN Files in Meno
54
+
55
+ In Meno, add the CDN URLs as page or project libraries. If editing JSON directly, put them in `meta.libraries`:
56
+
57
+ ```json
58
+ {
59
+ "meta": {
60
+ "libraries": {
61
+ "css": [
62
+ { "url": "https://cdn.jsdelivr.net/npm/meno-interactions@0.1.0/dist/meno-interactions.css" }
63
+ ],
64
+ "js": [
65
+ { "url": "https://cdn.jsdelivr.net/npm/meno-interactions@0.1.0/dist/meno-interactions.umd.js", "mode": "defer" }
66
+ ]
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ The script auto-initializes on page load. You do not need to write JavaScript for normal use.
73
+
74
+ ### Self-hosted CDN-style Setup
75
+
76
+ If you do not want third-party CDN URLs, copy these files into your Meno project's `/libraries/` folder:
77
+
78
+ - `dist/meno-interactions.umd.js`
79
+ - `dist/meno-interactions.css`
80
+
81
+ Then load them like this:
82
+
83
+ ```json
84
+ {
85
+ "meta": {
86
+ "libraries": {
87
+ "css": [{ "url": "/libraries/meno-interactions.css" }],
88
+ "js": [{ "url": "/libraries/meno-interactions.umd.js", "mode": "defer" }]
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Quick Use
95
+
96
+ Add attributes in Meno's Attributes panel. For example, an accordion only needs a wrapper and trigger attributes:
97
+
98
+ ```html
99
+ <div data-meno-accordion data-meno-single="true">
100
+ <button data-meno-accordion-trigger>Question</button>
101
+ <div>Answer</div>
102
+ </div>
103
+ ```
104
+
105
+ In Meno JSON, the same idea is represented through `attributes`:
106
+
107
+ ```json
108
+ {
109
+ "type": "node",
110
+ "tag": "div",
111
+ "attributes": { "data-meno-accordion": "", "data-meno-single": "true" },
112
+ "children": [
113
+ {
114
+ "type": "node",
115
+ "tag": "button",
116
+ "attributes": { "data-meno-accordion-trigger": "" },
117
+ "children": "Question"
118
+ },
119
+ { "type": "node", "tag": "div", "children": "Answer" }
120
+ ]
121
+ }
122
+ ```
123
+
124
+ ## Attribute Reference and Examples
125
+
126
+ ### Disclosure / Simple Toggle
127
+
128
+ Use this for one button that opens or closes one nearby panel.
129
+
130
+ ```html
131
+ <button data-meno-toggle data-meno-target="#details">Show details</button>
132
+ <div id="details" hidden>
133
+ Extra content goes here.
134
+ </div>
135
+ ```
136
+
137
+ Attributes:
138
+
139
+ - `data-meno-toggle`: marks the clickable trigger.
140
+ - `data-meno-target="#details"`: optional selector for the panel. If omitted, the next sibling is used.
141
+
142
+ State classes and attributes:
143
+
144
+ - The panel receives `.is-open` when open.
145
+ - The trigger receives `aria-expanded="true"` or `aria-expanded="false"`.
146
+ - The panel `hidden` attribute is toggled.
147
+
148
+ ### Accordion
149
+
150
+ Use this for FAQ rows or any stacked expandable content.
151
+
152
+ ```html
153
+ <div data-meno-accordion data-meno-single="true">
154
+ <button data-meno-accordion-trigger>What is included?</button>
155
+ <div hidden>Everything needed for the starter setup.</div>
156
+
157
+ <button data-meno-accordion-trigger>Can I open many rows?</button>
158
+ <div hidden>Set data-meno-single="false" on the wrapper.</div>
159
+ </div>
160
+ ```
161
+
162
+ Attributes:
163
+
164
+ - `data-meno-accordion`: marks the accordion wrapper.
165
+ - `data-meno-accordion-trigger`: marks each clickable row header.
166
+ - `data-meno-single="true"`: only one panel can be open at a time.
167
+ - `data-meno-single="false"`: multiple panels can stay open.
168
+
169
+ Panel rule:
170
+
171
+ - Each trigger controls its next sibling unless the trigger has `aria-controls="panel-id"`.
172
+
173
+ ### Tabs
174
+
175
+ Use this for switching between multiple panels.
176
+
177
+ ```html
178
+ <div data-meno-tabs data-meno-initial-tab="overview">
179
+ <div role="tablist">
180
+ <button data-meno-tab="overview">Overview</button>
181
+ <button data-meno-tab="specs">Specs</button>
182
+ <button data-meno-tab="faq">FAQ</button>
183
+ </div>
184
+
185
+ <section data-meno-tab-panel="overview">Overview content.</section>
186
+ <section data-meno-tab-panel="specs">Specs content.</section>
187
+ <section data-meno-tab-panel="faq">FAQ content.</section>
188
+ </div>
189
+ ```
190
+
191
+ Attributes:
192
+
193
+ - `data-meno-tabs`: marks the tab set wrapper.
194
+ - `data-meno-tab="id"`: marks a tab button.
195
+ - `data-meno-tab-panel="id"`: marks the matching panel.
196
+ - `data-meno-initial-tab="id"`: optional initial tab. If omitted, the first tab is selected.
197
+
198
+ State:
199
+
200
+ - Active tab and panel receive `.is-active`.
201
+ - Inactive panels receive `hidden`.
202
+ - Buttons receive `aria-selected`.
203
+
204
+ ### Dropdown
205
+
206
+ Use this for nav menus or compact option panels.
207
+
208
+ ```html
209
+ <nav>
210
+ <button data-meno-dropdown="services">Services</button>
211
+ <div data-meno-dropdown-panel="services">
212
+ <a href="/design">Design</a>
213
+ <a href="/development">Development</a>
214
+ </div>
215
+ </nav>
216
+ ```
217
+
218
+ Attributes:
219
+
220
+ - `data-meno-dropdown="services"`: trigger key.
221
+ - `data-meno-dropdown-panel="services"`: matching panel key.
222
+
223
+ State:
224
+
225
+ - The panel receives `.is-open`.
226
+ - The trigger receives `aria-expanded`.
227
+ - Clicking outside the root closes open dropdowns.
228
+
229
+ ### Modal
230
+
231
+ Use this for centered overlays, signup prompts, video popups, and confirmations.
232
+
233
+ ```html
234
+ <button data-meno-open-modal="signup">Open signup</button>
235
+
236
+ <div data-meno-modal="signup" data-meno-backdrop-close="true">
237
+ <div data-meno-modal-panel>
238
+ <button data-meno-close aria-label="Close">Close</button>
239
+ <h2>Join the list</h2>
240
+ <p>Modal content goes here.</p>
241
+ </div>
242
+ </div>
243
+ ```
244
+
245
+ Attributes:
246
+
247
+ - `data-meno-open-modal="signup"`: opens a modal with the same key.
248
+ - `data-meno-modal="signup"`: modal wrapper.
249
+ - `data-meno-close`: closes the nearest modal.
250
+ - `data-meno-backdrop-close="true"`: clicking the backdrop closes the modal.
251
+ - `data-meno-backdrop-close="false"`: disables backdrop close.
252
+
253
+ State:
254
+
255
+ - The modal receives `.is-open`.
256
+ - The modal `hidden` attribute is toggled.
257
+ - Body scrolling is locked while a modal is open.
258
+ - Escape closes open modals.
259
+
260
+ ### Drawer / Mobile Nav
261
+
262
+ Use this for side panels, mobile menus, filter panels, or cart drawers.
263
+
264
+ ```html
265
+ <button data-meno-open-drawer="menu">Menu</button>
266
+
267
+ <aside data-meno-drawer="menu">
268
+ <button data-meno-close aria-label="Close menu">Close</button>
269
+ <a href="/">Home</a>
270
+ <a href="/about">About</a>
271
+ </aside>
272
+ ```
273
+
274
+ Attributes:
275
+
276
+ - `data-meno-open-drawer="menu"`: opens a drawer with the same key.
277
+ - `data-meno-drawer="menu"`: drawer wrapper.
278
+ - `data-meno-close`: closes the drawer.
279
+
280
+ Meno agent compatibility:
281
+
282
+ - `data-action="toggle-mobile-nav"` is also treated as a drawer opener.
283
+ - `data-action="open-drawer"` is also supported.
284
+
285
+ State:
286
+
287
+ - The drawer receives `.is-open`.
288
+ - The drawer `hidden` attribute is toggled.
289
+ - Body scrolling is locked while a drawer is open.
290
+ - Escape closes open drawers.
291
+
292
+ ### Carousel
293
+
294
+ Use this for horizontally scrolling cards, logos, images, or testimonials.
295
+
296
+ ```html
297
+ <div data-meno-carousel>
298
+ <button data-meno-carousel-prev aria-label="Previous">Previous</button>
299
+
300
+ <div data-meno-carousel-track>
301
+ <article data-meno-carousel-item>Slide 1</article>
302
+ <article data-meno-carousel-item>Slide 2</article>
303
+ <article data-meno-carousel-item>Slide 3</article>
304
+ </div>
305
+
306
+ <button data-meno-carousel-next aria-label="Next">Next</button>
307
+ </div>
308
+ ```
309
+
310
+ Attributes:
311
+
312
+ - `data-meno-carousel`: marks the carousel wrapper.
313
+ - `data-meno-carousel-track`: marks the scrollable track.
314
+ - `data-meno-carousel-item`: optional item marker for scroll snapping styles.
315
+ - `data-meno-carousel-prev`: previous button.
316
+ - `data-meno-carousel-next`: next button.
317
+
318
+ Meno agent compatibility:
319
+
320
+ - `data-el="carousel-track"` is also supported.
321
+ - `data-action="carousel-prev"` and `data-action="carousel-next"` are also supported.
322
+
323
+ Behavior:
324
+
325
+ - Prev/next scroll by one item width.
326
+ - The CSS file adds smooth horizontal scroll and scroll snapping.
327
+
328
+ ### Sticky Header
329
+
330
+ Use this to add a class after the visitor scrolls.
331
+
332
+ ```html
333
+ <header data-meno-sticky data-meno-sticky-threshold="16">
334
+ Site header
335
+ </header>
336
+ ```
337
+
338
+ Attributes:
339
+
340
+ - `data-meno-sticky`: watches scroll position.
341
+ - `data-meno-sticky-threshold="16"`: optional pixel threshold. Default is `8`.
342
+
343
+ Meno agent compatibility:
344
+
345
+ - `data-el="site-header"` is also treated as sticky.
346
+
347
+ State:
348
+
349
+ - The header receives `.is-scrolled` after the threshold.
350
+
351
+ ### Smooth Scroll
352
+
353
+ Use this for in-page anchor navigation.
354
+
355
+ ```html
356
+ <nav data-meno-smooth-scroll>
357
+ <a href="#features">Features</a>
358
+ <a href="#pricing">Pricing</a>
359
+ </nav>
360
+
361
+ <section id="features">Features content</section>
362
+ <section id="pricing">Pricing content</section>
363
+ ```
364
+
365
+ Attributes:
366
+
367
+ - `data-meno-smooth-scroll`: put this on a wrapper containing anchor links, or directly on an anchor.
368
+
369
+ Behavior:
370
+
371
+ - Links starting with `#` scroll smoothly to matching element IDs.
372
+ - The URL hash is updated without a full jump.
373
+
374
+ ### Copy to Clipboard
375
+
376
+ Use this for copying codes, links, email addresses, or snippets.
377
+
378
+ ```html
379
+ <code id="coupon">SAVE20</code>
380
+ <button data-meno-copy="#coupon" data-meno-copied-label="Copied!">Copy code</button>
381
+ ```
382
+
383
+ You can also copy a literal value without a source element:
384
+
385
+ ```html
386
+ <button data-meno-copy data-meno-copy-text="hello@example.com">Copy email</button>
387
+ ```
388
+
389
+ Attributes:
390
+
391
+ - `data-meno-copy="#coupon"`: selector or ID of the source element.
392
+ - `data-meno-copy-text="value"`: fallback literal text to copy.
393
+ - `data-meno-copied-label="Copied!"`: temporary success label.
394
+ - `data-meno-copy-timeout="1500"`: how long the success label stays, in milliseconds.
395
+
396
+ Meno agent compatibility:
397
+
398
+ - `data-action="copy"` and `data-copy-target="coupon"` are also supported.
399
+
400
+ ### Form State
401
+
402
+ Use this for simple no-backend validation and success state.
403
+
404
+ ```html
405
+ <form data-meno-form data-meno-success="Thanks, we received it.">
406
+ <label>
407
+ Email
408
+ <input name="email" type="email" required data-meno-error="Email is required">
409
+ </label>
410
+
411
+ <button type="submit">Submit</button>
412
+ <p data-meno-form-status></p>
413
+ </form>
414
+ ```
415
+
416
+ Attributes:
417
+
418
+ - `data-meno-form`: marks a form for handling.
419
+ - `data-meno-form-status`: element where error/success text appears.
420
+ - `data-meno-success="message"`: success message.
421
+ - `data-meno-reset-on-submit="true"`: reset form after success. Default is `true`.
422
+ - `data-meno-reset-on-submit="false"`: keep form values after success.
423
+ - `data-meno-error="message"`: field-level required error message.
424
+
425
+ Behavior:
426
+
427
+ - The package prevents default submission.
428
+ - Required fields are checked.
429
+ - A `meno:form-submit` event is dispatched with `{ data }`.
430
+ - No network call is made. Wire your own backend by listening for `meno:form-submit`.
431
+
432
+ ### Lightbox
433
+
434
+ Use this for image gallery zoom.
435
+
436
+ ```html
437
+ <a href="/images/photo-large.webp" data-meno-open-lightbox data-meno-lightbox-alt="Project photo">
438
+ <img src="/images/photo-thumb.webp" alt="Project photo">
439
+ </a>
440
+
441
+ <div data-meno-lightbox hidden>
442
+ <button data-meno-close aria-label="Close">Close</button>
443
+ <img src="" alt="">
444
+ </div>
445
+ ```
446
+
447
+ Attributes:
448
+
449
+ - `data-meno-open-lightbox`: opens the lightbox.
450
+ - `data-meno-open-lightbox="/images/photo-large.webp"`: optional explicit image URL.
451
+ - `data-meno-lightbox-alt="Text"`: alt text for the opened image.
452
+ - `data-meno-lightbox`: lightbox wrapper.
453
+ - `data-meno-close`: close button.
454
+
455
+ State:
456
+
457
+ - The lightbox receives `.is-open`.
458
+ - The lightbox `hidden` attribute is toggled.
459
+
460
+ ### Generic Class Toggle
461
+
462
+ Use this when you only need to add, remove, or toggle a class.
463
+
464
+ ```html
465
+ <button data-meno-class-toggle="is-highlighted" data-meno-target="#card">
466
+ Toggle highlight
467
+ </button>
468
+
469
+ <article id="card">Card content</article>
470
+ ```
471
+
472
+ Attributes:
473
+
474
+ - `data-meno-class-toggle="class-name"`: toggles a class.
475
+ - `data-meno-class-add="class-name"`: adds a class.
476
+ - `data-meno-class-remove="class-name"`: removes a class.
477
+ - `data-meno-target="#selector"`: optional target. If omitted, the clicked element is used.
478
+
479
+ Examples:
480
+
481
+ ```html
482
+ <button data-meno-class-add="is-open" data-meno-target="#panel">Open</button>
483
+ <button data-meno-class-remove="is-open" data-meno-target="#panel">Close</button>
484
+ <div id="panel">Panel</div>
485
+ ```
486
+
487
+ ### Custom Event Dispatch
488
+
489
+ Use this to let no-code controls trigger custom JavaScript elsewhere.
490
+
491
+ ```html
492
+ <button data-meno-dispatch="pricing:select">Choose plan</button>
493
+
494
+ <script>
495
+ document.addEventListener('pricing:select', function (event) {
496
+ console.log('Selected from', event.detail.source);
497
+ });
498
+ </script>
499
+ ```
500
+
501
+ Attributes:
502
+
503
+ - `data-meno-dispatch="event-name"`: dispatches a bubbling custom event from the clicked element.
504
+
505
+ ## MenoFilter
506
+
507
+ MenoFilter is built into Meno and already works with attributes such as `data-meno-filter`, `data-meno-list`, `data-meno-search`, `data-meno-sort`, and `data-meno-page`. This package does not replace it; it complements it for UI interactions outside filtering.
508
+
509
+ Common MenoFilter attributes:
510
+
511
+ ```html
512
+ <div data-meno-filter="posts" data-meno-url-sync="true" data-meno-per-page="6">
513
+ <input data-meno-search data-meno-search-fields="title,excerpt" placeholder="Search">
514
+
515
+ <button data-meno-filter-field="category" data-meno-filter-value="Design">Design</button>
516
+ <button data-meno-clear>Clear</button>
517
+
518
+ <div data-meno-list>
519
+ <article data-category="Design" data-title="Example post">Example post</article>
520
+ </div>
521
+
522
+ <p><span data-meno-count="results"></span> results</p>
523
+ <div data-meno-empty>No results found.</div>
524
+ </div>
525
+ ```
526
+
527
+ ## Interactive Styles Pattern
528
+
529
+ Use the package to toggle state classes, then style those states in Meno `interactiveStyles` or component CSS.
530
+
531
+ ```json
532
+ {
533
+ "interactiveStyles": [
534
+ {
535
+ "name": "Open panel",
536
+ "prefix": "",
537
+ "postfix": ".is-open",
538
+ "style": { "base": { "display": "block", "opacity": "1" } }
539
+ }
540
+ ]
541
+ }
542
+ ```
543
+
544
+ For child targets, put the interactive style on the target node and use a parent selector:
545
+
546
+ ```json
547
+ {
548
+ "prefix": "[data-el='dropdown']:hover ",
549
+ "postfix": "",
550
+ "style": { "base": { "opacity": "1", "visibility": "visible" } }
551
+ }
552
+ ```
553
+
554
+ ## Meno Agent Compatibility
555
+
556
+ The script recognizes Meno agent conventions as aliases:
557
+
558
+ | Agent convention | Package behavior |
559
+ | --- | --- |
560
+ | `data-action="toggle-accordion"` | Accordion trigger |
561
+ | `data-action="select-tab"` | Tab trigger |
562
+ | `data-el="tab-panel"` | Tab panel |
563
+ | `data-action="toggle-dropdown"` | Dropdown trigger |
564
+ | `data-el="dropdown-panel"` | Dropdown panel |
565
+ | `data-action="open-modal"` | Modal trigger |
566
+ | `data-action="close-modal"` | Modal close |
567
+ | `data-action="open-drawer"` | Drawer trigger |
568
+ | `data-action="close-drawer"` | Drawer close |
569
+ | `data-action="toggle-mobile-nav"` | Drawer/mobile nav trigger |
570
+ | `data-el="carousel-track"` | Carousel track |
571
+ | `data-action="carousel-prev"` | Carousel previous |
572
+ | `data-action="carousel-next"` | Carousel next |
573
+ | `data-el="site-header"` | Sticky header |
574
+ | `data-action="copy"` | Copy trigger |
575
+ | `data-copy-target="id"` | Copy source |
576
+ | `data-el="form-status"` | Form status element |
577
+
578
+ ## JavaScript API
579
+
580
+ ```js
581
+ import { init, destroy, register } from 'meno-interactions';
582
+
583
+ const app = init({ root: document });
584
+ app.refresh();
585
+ destroy();
586
+
587
+ register('my-interaction', function (root) {
588
+ const button = root.querySelector('[data-my-button]');
589
+ if (!button) return [];
590
+ const handler = function () {
591
+ console.log('Clicked');
592
+ };
593
+ button.addEventListener('click', handler);
594
+ return [function () { button.removeEventListener('click', handler); }];
595
+ });
596
+ ```
597
+
598
+ The browser build exposes `window.MenoInteractions`:
599
+
600
+ ```html
601
+ <script>
602
+ window.MenoInteractions.init();
603
+ </script>
604
+ ```
605
+
606
+ ## Build
607
+
608
+ ```bash
609
+ npm run build
610
+ npm test
611
+ ```
@@ -0,0 +1,25 @@
1
+ export interface MenoInteractionsOptions {
2
+ root?: Document | Element;
3
+ activeClass?: string;
4
+ openClass?: string;
5
+ scrolledClass?: string;
6
+ hiddenClass?: string;
7
+ reducedMotion?: boolean;
8
+ }
9
+
10
+ export interface MenoInteractionsInstance {
11
+ root: Document | Element | null;
12
+ destroy(): void;
13
+ refresh(): MenoInteractionsInstance;
14
+ }
15
+
16
+ export function init(options?: MenoInteractionsOptions): MenoInteractionsInstance;
17
+ export function destroy(root?: Document | Element): void;
18
+ export function register(name: string, initializer: (root: Document | Element, options: MenoInteractionsOptions) => Array<() => void> | void): void;
19
+
20
+ export const MenoInteractions: {
21
+ init: typeof init;
22
+ destroy: typeof destroy;
23
+ register: typeof register;
24
+ version: string;
25
+ };