# Meno Interactions

Attribute-driven JavaScript interactions for Meno projects and static no-code sites.

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.

## Install

```bash
npm install meno-interactions
```

Or use the browser files from `dist/` in a Meno project:

```json
{
  "meta": {
    "libraries": {
      "js": [{ "url": "/libraries/meno-interactions.umd.js", "mode": "defer" }],
      "css": [{ "url": "/libraries/meno-interactions.css" }]
    }
  }
}
```

## CDN Setup

After this package is published to npm, you can load it from a CDN with one CSS file and one deferred script.

### Option 1: jsDelivr

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/meno-interactions/dist/meno-interactions.css">
<script defer src="https://cdn.jsdelivr.net/npm/meno-interactions/dist/meno-interactions.umd.js"></script>
```

### Option 2: unpkg

```html
<link rel="stylesheet" href="https://unpkg.com/meno-interactions/dist/meno-interactions.css">
<script defer src="https://unpkg.com/meno-interactions/dist/meno-interactions.umd.js"></script>
```

### Option 3: Version-pin the CDN

Pinning a version prevents future package updates from changing an already-launched site.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/meno-interactions@0.1.0/dist/meno-interactions.css">
<script defer src="https://cdn.jsdelivr.net/npm/meno-interactions@0.1.0/dist/meno-interactions.umd.js"></script>
```

### Add CDN Files in Meno

In Meno, add the CDN URLs as page or project libraries. If editing JSON directly, put them in `meta.libraries`:

```json
{
  "meta": {
    "libraries": {
      "css": [
        { "url": "https://cdn.jsdelivr.net/npm/meno-interactions@0.1.0/dist/meno-interactions.css" }
      ],
      "js": [
        { "url": "https://cdn.jsdelivr.net/npm/meno-interactions@0.1.0/dist/meno-interactions.umd.js", "mode": "defer" }
      ]
    }
  }
}
```

The script auto-initializes on page load. You do not need to write JavaScript for normal use.

### Self-hosted CDN-style Setup

If you do not want third-party CDN URLs, copy these files into your Meno project's `/libraries/` folder:

- `dist/meno-interactions.umd.js`
- `dist/meno-interactions.css`

Then load them like this:

```json
{
  "meta": {
    "libraries": {
      "css": [{ "url": "/libraries/meno-interactions.css" }],
      "js": [{ "url": "/libraries/meno-interactions.umd.js", "mode": "defer" }]
    }
  }
}
```

## Quick Use

Add attributes in Meno's Attributes panel. For example, an accordion only needs a wrapper and trigger attributes:

```html
<div data-meno-accordion data-meno-single="true">
  <button data-meno-accordion-trigger>Question</button>
  <div>Answer</div>
</div>
```

In Meno JSON, the same idea is represented through `attributes`:

```json
{
  "type": "node",
  "tag": "div",
  "attributes": { "data-meno-accordion": "", "data-meno-single": "true" },
  "children": [
    {
      "type": "node",
      "tag": "button",
      "attributes": { "data-meno-accordion-trigger": "" },
      "children": "Question"
    },
    { "type": "node", "tag": "div", "children": "Answer" }
  ]
}
```

## Attribute Reference and Examples

### Disclosure / Simple Toggle

Use this for one button that opens or closes one nearby panel.

```html
<button data-meno-toggle data-meno-target="#details">Show details</button>
<div id="details" hidden>
  Extra content goes here.
</div>
```

Attributes:

- `data-meno-toggle`: marks the clickable trigger.
- `data-meno-target="#details"`: optional selector for the panel. If omitted, the next sibling is used.

State classes and attributes:

- The panel receives `.is-open` when open.
- The trigger receives `aria-expanded="true"` or `aria-expanded="false"`.
- The panel `hidden` attribute is toggled.

### Accordion

Use this for FAQ rows or any stacked expandable content.

```html
<div data-meno-accordion data-meno-single="true">
  <button data-meno-accordion-trigger>What is included?</button>
  <div hidden>Everything needed for the starter setup.</div>

  <button data-meno-accordion-trigger>Can I open many rows?</button>
  <div hidden>Set data-meno-single="false" on the wrapper.</div>
</div>
```

Attributes:

- `data-meno-accordion`: marks the accordion wrapper.
- `data-meno-accordion-trigger`: marks each clickable row header.
- `data-meno-single="true"`: only one panel can be open at a time.
- `data-meno-single="false"`: multiple panels can stay open.

Panel rule:

- Each trigger controls its next sibling unless the trigger has `aria-controls="panel-id"`.

### Tabs

Use this for switching between multiple panels.

```html
<div data-meno-tabs data-meno-initial-tab="overview">
  <div role="tablist">
    <button data-meno-tab="overview">Overview</button>
    <button data-meno-tab="specs">Specs</button>
    <button data-meno-tab="faq">FAQ</button>
  </div>

  <section data-meno-tab-panel="overview">Overview content.</section>
  <section data-meno-tab-panel="specs">Specs content.</section>
  <section data-meno-tab-panel="faq">FAQ content.</section>
</div>
```

Attributes:

- `data-meno-tabs`: marks the tab set wrapper.
- `data-meno-tab="id"`: marks a tab button.
- `data-meno-tab-panel="id"`: marks the matching panel.
- `data-meno-initial-tab="id"`: optional initial tab. If omitted, the first tab is selected.

State:

- Active tab and panel receive `.is-active`.
- Inactive panels receive `hidden`.
- Buttons receive `aria-selected`.

### Dropdown

Use this for nav menus or compact option panels.

```html
<nav>
  <button data-meno-dropdown="services">Services</button>
  <div data-meno-dropdown-panel="services">
    <a href="/design">Design</a>
    <a href="/development">Development</a>
  </div>
</nav>
```

Attributes:

- `data-meno-dropdown="services"`: trigger key.
- `data-meno-dropdown-panel="services"`: matching panel key.

State:

- The panel receives `.is-open`.
- The trigger receives `aria-expanded`.
- Clicking outside the root closes open dropdowns.

### Modal

Use this for centered overlays, signup prompts, video popups, and confirmations.

```html
<button data-meno-open-modal="signup">Open signup</button>

<div data-meno-modal="signup" data-meno-backdrop-close="true">
  <div data-meno-modal-panel>
    <button data-meno-close aria-label="Close">Close</button>
    <h2>Join the list</h2>
    <p>Modal content goes here.</p>
  </div>
</div>
```

Attributes:

- `data-meno-open-modal="signup"`: opens a modal with the same key.
- `data-meno-modal="signup"`: modal wrapper.
- `data-meno-close`: closes the nearest modal.
- `data-meno-backdrop-close="true"`: clicking the backdrop closes the modal.
- `data-meno-backdrop-close="false"`: disables backdrop close.

State:

- The modal receives `.is-open`.
- The modal `hidden` attribute is toggled.
- Body scrolling is locked while a modal is open.
- Escape closes open modals.

### Drawer / Mobile Nav

Use this for side panels, mobile menus, filter panels, or cart drawers.

```html
<button data-meno-open-drawer="menu">Menu</button>

<aside data-meno-drawer="menu">
  <button data-meno-close aria-label="Close menu">Close</button>
  <a href="/">Home</a>
  <a href="/about">About</a>
</aside>
```

Attributes:

- `data-meno-open-drawer="menu"`: opens a drawer with the same key.
- `data-meno-drawer="menu"`: drawer wrapper.
- `data-meno-close`: closes the drawer.

Meno agent compatibility:

- `data-action="toggle-mobile-nav"` is also treated as a drawer opener.
- `data-action="open-drawer"` is also supported.

State:

- The drawer receives `.is-open`.
- The drawer `hidden` attribute is toggled.
- Body scrolling is locked while a drawer is open.
- Escape closes open drawers.

### Carousel

Use this for horizontally scrolling cards, logos, images, or testimonials.

```html
<div data-meno-carousel>
  <button data-meno-carousel-prev aria-label="Previous">Previous</button>

  <div data-meno-carousel-track>
    <article data-meno-carousel-item>Slide 1</article>
    <article data-meno-carousel-item>Slide 2</article>
    <article data-meno-carousel-item>Slide 3</article>
  </div>

  <button data-meno-carousel-next aria-label="Next">Next</button>
</div>
```

Attributes:

- `data-meno-carousel`: marks the carousel wrapper.
- `data-meno-carousel-track`: marks the scrollable track.
- `data-meno-carousel-item`: optional item marker for scroll snapping styles.
- `data-meno-carousel-prev`: previous button.
- `data-meno-carousel-next`: next button.

Meno agent compatibility:

- `data-el="carousel-track"` is also supported.
- `data-action="carousel-prev"` and `data-action="carousel-next"` are also supported.

Behavior:

- Prev/next scroll by one item width.
- The CSS file adds smooth horizontal scroll and scroll snapping.

### Sticky Header

Use this to add a class after the visitor scrolls.

```html
<header data-meno-sticky data-meno-sticky-threshold="16">
  Site header
</header>
```

Attributes:

- `data-meno-sticky`: watches scroll position.
- `data-meno-sticky-threshold="16"`: optional pixel threshold. Default is `8`.

Meno agent compatibility:

- `data-el="site-header"` is also treated as sticky.

State:

- The header receives `.is-scrolled` after the threshold.

### Smooth Scroll

Use this for in-page anchor navigation.

```html
<nav data-meno-smooth-scroll>
  <a href="#features">Features</a>
  <a href="#pricing">Pricing</a>
</nav>

<section id="features">Features content</section>
<section id="pricing">Pricing content</section>
```

Attributes:

- `data-meno-smooth-scroll`: put this on a wrapper containing anchor links, or directly on an anchor.

Behavior:

- Links starting with `#` scroll smoothly to matching element IDs.
- The URL hash is updated without a full jump.

### Copy to Clipboard

Use this for copying codes, links, email addresses, or snippets.

```html
<code id="coupon">SAVE20</code>
<button data-meno-copy="#coupon" data-meno-copied-label="Copied!">Copy code</button>
```

You can also copy a literal value without a source element:

```html
<button data-meno-copy data-meno-copy-text="hello@example.com">Copy email</button>
```

Attributes:

- `data-meno-copy="#coupon"`: selector or ID of the source element.
- `data-meno-copy-text="value"`: fallback literal text to copy.
- `data-meno-copied-label="Copied!"`: temporary success label.
- `data-meno-copy-timeout="1500"`: how long the success label stays, in milliseconds.

Meno agent compatibility:

- `data-action="copy"` and `data-copy-target="coupon"` are also supported.

### Form State

Use this for simple no-backend validation and success state.

```html
<form data-meno-form data-meno-success="Thanks, we received it.">
  <label>
    Email
    <input name="email" type="email" required data-meno-error="Email is required">
  </label>

  <button type="submit">Submit</button>
  <p data-meno-form-status></p>
</form>
```

Attributes:

- `data-meno-form`: marks a form for handling.
- `data-meno-form-status`: element where error/success text appears.
- `data-meno-success="message"`: success message.
- `data-meno-reset-on-submit="true"`: reset form after success. Default is `true`.
- `data-meno-reset-on-submit="false"`: keep form values after success.
- `data-meno-error="message"`: field-level required error message.

Behavior:

- The package prevents default submission.
- Required fields are checked.
- A `meno:form-submit` event is dispatched with `{ data }`.
- No network call is made. Wire your own backend by listening for `meno:form-submit`.

### Lightbox

Use this for image gallery zoom.

```html
<a href="/images/photo-large.webp" data-meno-open-lightbox data-meno-lightbox-alt="Project photo">
  <img src="/images/photo-thumb.webp" alt="Project photo">
</a>

<div data-meno-lightbox hidden>
  <button data-meno-close aria-label="Close">Close</button>
  <img src="" alt="">
</div>
```

Attributes:

- `data-meno-open-lightbox`: opens the lightbox.
- `data-meno-open-lightbox="/images/photo-large.webp"`: optional explicit image URL.
- `data-meno-lightbox-alt="Text"`: alt text for the opened image.
- `data-meno-lightbox`: lightbox wrapper.
- `data-meno-close`: close button.

State:

- The lightbox receives `.is-open`.
- The lightbox `hidden` attribute is toggled.

### Generic Class Toggle

Use this when you only need to add, remove, or toggle a class.

```html
<button data-meno-class-toggle="is-highlighted" data-meno-target="#card">
  Toggle highlight
</button>

<article id="card">Card content</article>
```

Attributes:

- `data-meno-class-toggle="class-name"`: toggles a class.
- `data-meno-class-add="class-name"`: adds a class.
- `data-meno-class-remove="class-name"`: removes a class.
- `data-meno-target="#selector"`: optional target. If omitted, the clicked element is used.

Examples:

```html
<button data-meno-class-add="is-open" data-meno-target="#panel">Open</button>
<button data-meno-class-remove="is-open" data-meno-target="#panel">Close</button>
<div id="panel">Panel</div>
```

### Custom Event Dispatch

Use this to let no-code controls trigger custom JavaScript elsewhere.

```html
<button data-meno-dispatch="pricing:select">Choose plan</button>

<script>
  document.addEventListener('pricing:select', function (event) {
    console.log('Selected from', event.detail.source);
  });
</script>
```

Attributes:

- `data-meno-dispatch="event-name"`: dispatches a bubbling custom event from the clicked element.

## MenoFilter

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.

Common MenoFilter attributes:

```html
<div data-meno-filter="posts" data-meno-url-sync="true" data-meno-per-page="6">
  <input data-meno-search data-meno-search-fields="title,excerpt" placeholder="Search">

  <button data-meno-filter-field="category" data-meno-filter-value="Design">Design</button>
  <button data-meno-clear>Clear</button>

  <div data-meno-list>
    <article data-category="Design" data-title="Example post">Example post</article>
  </div>

  <p><span data-meno-count="results"></span> results</p>
  <div data-meno-empty>No results found.</div>
</div>
```

## Interactive Styles Pattern

Use the package to toggle state classes, then style those states in Meno `interactiveStyles` or component CSS.

```json
{
  "interactiveStyles": [
    {
      "name": "Open panel",
      "prefix": "",
      "postfix": ".is-open",
      "style": { "base": { "display": "block", "opacity": "1" } }
    }
  ]
}
```

For child targets, put the interactive style on the target node and use a parent selector:

```json
{
  "prefix": "[data-el='dropdown']:hover ",
  "postfix": "",
  "style": { "base": { "opacity": "1", "visibility": "visible" } }
}
```

## Meno Agent Compatibility

The script recognizes Meno agent conventions as aliases:

| Agent convention | Package behavior |
| --- | --- |
| `data-action="toggle-accordion"` | Accordion trigger |
| `data-action="select-tab"` | Tab trigger |
| `data-el="tab-panel"` | Tab panel |
| `data-action="toggle-dropdown"` | Dropdown trigger |
| `data-el="dropdown-panel"` | Dropdown panel |
| `data-action="open-modal"` | Modal trigger |
| `data-action="close-modal"` | Modal close |
| `data-action="open-drawer"` | Drawer trigger |
| `data-action="close-drawer"` | Drawer close |
| `data-action="toggle-mobile-nav"` | Drawer/mobile nav trigger |
| `data-el="carousel-track"` | Carousel track |
| `data-action="carousel-prev"` | Carousel previous |
| `data-action="carousel-next"` | Carousel next |
| `data-el="site-header"` | Sticky header |
| `data-action="copy"` | Copy trigger |
| `data-copy-target="id"` | Copy source |
| `data-el="form-status"` | Form status element |

## JavaScript API

```js
import { init, destroy, register } from 'meno-interactions';

const app = init({ root: document });
app.refresh();
destroy();

register('my-interaction', function (root) {
  const button = root.querySelector('[data-my-button]');
  if (!button) return [];
  const handler = function () {
    console.log('Clicked');
  };
  button.addEventListener('click', handler);
  return [function () { button.removeEventListener('click', handler); }];
});
```

The browser build exposes `window.MenoInteractions`:

```html
<script>
  window.MenoInteractions.init();
</script>
```

## Build

```bash
npm run build
npm test
```
