@ktfth/stickjs 3.0.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/CHANGELOG.md +169 -0
- package/README.md +449 -0
- package/bin/registry.json +20 -0
- package/bin/stickjs.js +158 -0
- package/llms.txt +244 -0
- package/package.json +38 -0
- package/stick-ui/components/accordion.html +25 -0
- package/stick-ui/components/autocomplete.html +82 -0
- package/stick-ui/components/command-palette.html +28 -0
- package/stick-ui/components/copy-button.html +12 -0
- package/stick-ui/components/data-table.html +191 -0
- package/stick-ui/components/dialog.html +23 -0
- package/stick-ui/components/dropdown.html +16 -0
- package/stick-ui/components/notification.html +11 -0
- package/stick-ui/components/skeleton.html +11 -0
- package/stick-ui/components/stepper.html +102 -0
- package/stick-ui/components/tabs.html +26 -0
- package/stick-ui/components/toast.html +10 -0
- package/stick-ui/components/toggle-group.html +16 -0
- package/stick-ui/components/toggle.html +9 -0
- package/stick-ui/components/tooltip.html +12 -0
- package/stick-ui/plugins/autocomplete.js +422 -0
- package/stick-ui/plugins/command-palette.js +289 -0
- package/stick-ui/plugins/data-table.js +426 -0
- package/stick-ui/plugins/dropdown.js +70 -0
- package/stick-ui/plugins/stepper.js +155 -0
- package/stick-ui/plugins/toast.js +51 -0
- package/stick-ui/plugins/tooltip.js +67 -0
- package/stick-ui/stick-ui.css +825 -0
- package/stick.d.ts +105 -0
- package/stick.js +655 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Stick.js will be documented here.
|
|
4
|
+
|
|
5
|
+
## v3.1.0 — CLI + CDN + Vercel docs
|
|
6
|
+
|
|
7
|
+
### New features
|
|
8
|
+
- **CLI** — `npx stickjs add <component>` copies component files to `./stick-ui/` (shadcn/ui-style)
|
|
9
|
+
- **`npx stickjs list`** — list all 15 available components
|
|
10
|
+
- **`npx stickjs add --all`** — add all components at once
|
|
11
|
+
- **`--force` flag** — overwrite existing files
|
|
12
|
+
- **CDN** — all files available via jsdelivr and unpkg after npm publish
|
|
13
|
+
- **Release script** — `npm run release` minifies, publishes to npm, and deploys docs to Vercel
|
|
14
|
+
|
|
15
|
+
### Infrastructure
|
|
16
|
+
- Vercel deployment for documentation site (`docs/vercel.json`)
|
|
17
|
+
- `bin/registry.json` component manifest
|
|
18
|
+
- `bin/stickjs.js` zero-dependency CLI (Node built-ins only)
|
|
19
|
+
|
|
20
|
+
## v3.0.0 — core hardening + a11y + transitions
|
|
21
|
+
|
|
22
|
+
### New features
|
|
23
|
+
- **Error boundary** — handler failures are caught and logged; one failing handler no longer breaks siblings on the same element
|
|
24
|
+
- **`Stick.fire(el, handler, param?)`** — invoke any handler programmatically without events (chainable)
|
|
25
|
+
- **`Stick.on(event, fn)`** — lifecycle hooks for `bind` and `unbind` events (chainable)
|
|
26
|
+
- **`data-stick-group="name"`** — mutual exclusivity: `show`/`toggle`/`show-modal` hides others in the same group
|
|
27
|
+
- **`data-stick-transition="name"`** — CSS enter/leave transitions: adds `name-enter-active` / `name-leave-active` classes with automatic cleanup
|
|
28
|
+
- **`set-aria` handler** — set ARIA attributes declaratively, auto-prefixes `aria-`
|
|
29
|
+
- **`toggle-aria` handler** — flip ARIA boolean attributes (`true` ↔ `false`)
|
|
30
|
+
|
|
31
|
+
### Improvements
|
|
32
|
+
- `show`/`hide`/`toggle` auto-manage `aria-expanded` on trigger element
|
|
33
|
+
- All handler invocations wrapped in try/catch (error boundary)
|
|
34
|
+
|
|
35
|
+
## [2.9.0] — 2026-02-19
|
|
36
|
+
|
|
37
|
+
### Added (iter 20)
|
|
38
|
+
- `clone-template` now resolves `{{tokens}}` from the trigger element inside cloned text nodes and attributes — dynamic list generation fully declarative
|
|
39
|
+
- `sort` handler — sort `target.children` alphabetically by textContent (default) or any `data-*`/attribute key (numeric-aware)
|
|
40
|
+
- `count` handler — set `target.textContent` to count of elements matching CSS selector param, or `target.children.length` when param is empty
|
|
41
|
+
- `data-stick-swap` now accepts `"prepend"` (alias for `afterbegin`) and `"append"` (alias for `beforeend`) for more natural naming
|
|
42
|
+
- 6 new browser tests
|
|
43
|
+
|
|
44
|
+
## [2.8.0] — 2026-02-19
|
|
45
|
+
|
|
46
|
+
### Added (iter 19)
|
|
47
|
+
- `data-stick-delegate="selector"` — event delegation: attach one listener to a container, fire for matching descendants. `el` in the handler is the matched child. Relative targets (`parent`, `next`, `closest:`) resolve from the matched child.
|
|
48
|
+
- `{{url:key}}` interpolation — reads `URLSearchParams` from the current page URL at event time
|
|
49
|
+
- `scroll-top` handler — `window.scrollTo({ top: 0, behavior: 'smooth' })` — back-to-top in one attribute
|
|
50
|
+
- 9 new browser tests
|
|
51
|
+
|
|
52
|
+
## [2.7.0] — 2026-02-19
|
|
53
|
+
|
|
54
|
+
### Added (iter 18)
|
|
55
|
+
- `parse()` shorthand — `"event:handler"` (no trailing colon) now valid; trailing colon remains optional
|
|
56
|
+
- `watch` synthetic event — fires immediately on bind, then on every attribute mutation of `el` (MutationObserver)
|
|
57
|
+
- `select` handler — `target.select()` — focus and select all text in an input/textarea
|
|
58
|
+
- `history-push` handler — `window.history.pushState({}, '', param)` — update URL without page reload
|
|
59
|
+
- 8 new browser tests covering all new features
|
|
60
|
+
|
|
61
|
+
## [2.6.0] — 2026-02-20
|
|
62
|
+
|
|
63
|
+
### Added (iter 17)
|
|
64
|
+
- `{{chars}}` interpolation — `el.value.length` (character counter for inputs/textareas)
|
|
65
|
+
- `Stick.debug(true?)` — enable verbose console logging (bind + fire events); chainable
|
|
66
|
+
- Comprehensive demo rewrite (`index.html`) — 16 sections covering every major feature:
|
|
67
|
+
tabs with siblings, native dialog, checkboxes, counters, clone-template,
|
|
68
|
+
animate, store+ready, dispatch, plugin system, multi-target, intersect, observe()
|
|
69
|
+
- 4 new browser tests for `{{chars}}` and `Stick.debug()`
|
|
70
|
+
|
|
71
|
+
## [2.5.0] — 2026-02-20
|
|
72
|
+
|
|
73
|
+
### Added (iter 16)
|
|
74
|
+
- `Stick.use(plugin)` — plugin system: fn(stick) | { install } | { name: fn, … }
|
|
75
|
+
- `Stick.unbind(el)` — remove all listeners from el, clear data-stick-bound (enables clean SPA unmount)
|
|
76
|
+
- `{{index}}` interpolation — element's position among parent's children (0-based)
|
|
77
|
+
- `{{length}}` interpolation — `el.children.length`
|
|
78
|
+
- `{{attr}}` interpolation — any attribute via `el.getAttribute(attr)` (always worked, now documented)
|
|
79
|
+
- Updated `stick.d.ts` — full TypeScript coverage: `StickPlugin`, `StickSyntheticEvent`, `use`, `unbind`
|
|
80
|
+
- Internal: `listenerMap` WeakMap tracks attached listeners for `unbind` cleanup
|
|
81
|
+
- 12 new browser tests
|
|
82
|
+
|
|
83
|
+
## [2.4.0] — 2026-02-20
|
|
84
|
+
|
|
85
|
+
### Added (iter 15)
|
|
86
|
+
- Multi-target support: `"siblings"` and `"all:sel"` target keywords — handler called once per matched element
|
|
87
|
+
- `ready` synthetic event — fires immediately on `bind()`, before any user interaction
|
|
88
|
+
- `animate` handler — adds CSS class, auto-removes after `animationend`
|
|
89
|
+
- Internal: `resolveTarget` → `resolveTargets` (returns array); enables true multi-target dispatch
|
|
90
|
+
- 8 new browser tests (siblings, all:, ready, animate)
|
|
91
|
+
|
|
92
|
+
## [2.3.0] — 2026-02-20
|
|
93
|
+
|
|
94
|
+
### Added (iter 14)
|
|
95
|
+
- `toggle-attr` handler — toggle attribute presence (great for `open`, `disabled`, `aria-expanded`)
|
|
96
|
+
- `clone-template` handler — clone `<template>` content into target; auto-binds new elements
|
|
97
|
+
- `data-stick-prevent` modifier — always call `event.preventDefault()` (saves a stacked slot on forms)
|
|
98
|
+
- `data-stick-stop` modifier — always call `event.stopPropagation()`
|
|
99
|
+
- `"self"` target keyword — explicit self-reference in `data-stick-target`
|
|
100
|
+
- `Stick.remove(name)` API — unregister a handler, chainable
|
|
101
|
+
- 10 new browser tests
|
|
102
|
+
|
|
103
|
+
## [2.2.0] — 2026-02-20
|
|
104
|
+
|
|
105
|
+
### Added (iter 13)
|
|
106
|
+
- `data-stick-key="Enter"` modifier — filter keyboard events by key name (comma-separated)
|
|
107
|
+
- `open` handler — `window.open(param, '_blank', 'noopener')`
|
|
108
|
+
- `back` / `forward` handlers — history navigation
|
|
109
|
+
- `show-modal` / `close-modal` handlers — native `<dialog>` support
|
|
110
|
+
- 10 new browser tests
|
|
111
|
+
|
|
112
|
+
## [2.1.0] — 2026-02-20
|
|
113
|
+
|
|
114
|
+
### Added (iter 12)
|
|
115
|
+
- `check`, `uncheck`, `toggle-check` handlers — checkbox control
|
|
116
|
+
- `increment`, `decrement` handlers — counter mutation on text/input targets (optional step param)
|
|
117
|
+
- `set-data` handler — `target.dataset[key] = value` (param: `"key:value"`)
|
|
118
|
+
- `data-stick-passive` modifier — `addEventListener` with `passive: true` (performance for scroll/touch)
|
|
119
|
+
- `data-stick-capture` modifier — `addEventListener` with `capture: true`
|
|
120
|
+
- 13 new browser tests covering all new handlers and modifiers
|
|
121
|
+
|
|
122
|
+
## [2.0.0] — 2026-02-20
|
|
123
|
+
|
|
124
|
+
Complete rewrite. Zero dependencies. Vanilla JS.
|
|
125
|
+
|
|
126
|
+
### Added
|
|
127
|
+
- `data-stick="event:handler:param"` core syntax
|
|
128
|
+
- `data-stick-target` — apply handler to another element
|
|
129
|
+
- Relative targets: `"next"`, `"prev"`, `"parent"`, `"closest:sel"`
|
|
130
|
+
- `data-stick-2/3/4/5` — stack multiple behaviors on one element
|
|
131
|
+
- `data-stick-target-2/3/4/5` — per-slot targets for stacked behaviors
|
|
132
|
+
- `data-stick-once` — remove listener after first fire
|
|
133
|
+
- `data-stick-debounce="ms"` — debounce handler
|
|
134
|
+
- `data-stick-throttle="ms"` — throttle handler
|
|
135
|
+
- `data-stick-confirm="msg"` — window.confirm gate
|
|
136
|
+
- `data-stick-bound` attribute — prevents double-binding; remove to rebind
|
|
137
|
+
- `{{value}}`, `{{text}}`, `{{id}}`, `{{checked}}`, `{{data-*}}` param interpolation
|
|
138
|
+
- `intersect` synthetic event (IntersectionObserver)
|
|
139
|
+
- `Stick.observe()` — MutationObserver auto-bind
|
|
140
|
+
- `Stick.version`, `Stick.handlers`, `Stick.parse()`, `Stick.bind()`
|
|
141
|
+
- UMD wrapper — works as `<script>`, `require()`, or `import`
|
|
142
|
+
- TypeScript types (`stick.d.ts`)
|
|
143
|
+
- `llms.txt` — machine-readable API reference for LLMs
|
|
144
|
+
|
|
145
|
+
### Built-in handlers (40)
|
|
146
|
+
`show`, `hide`, `toggle`, `add-class`, `remove-class`, `toggle-class`,
|
|
147
|
+
`set-text`, `set-html`, `set-value`, `clear`, `set-attr`, `remove-attr`,
|
|
148
|
+
`set-style`, `focus`, `scroll-to`, `copy`, `navigate`, `reload`, `print`,
|
|
149
|
+
`submit`, `reset`, `prevent`, `stop`, `dispatch`, `emit`, `remove`,
|
|
150
|
+
`disable`, `enable`, `store`, `restore`, `fetch`, `log`, `alert`, `wait`
|
|
151
|
+
|
|
152
|
+
### fetch handler options
|
|
153
|
+
- `data-stick-method` — HTTP method (default: GET)
|
|
154
|
+
- `data-stick-swap` — insertion mode (innerHTML, outerHTML, beforeend, …)
|
|
155
|
+
- `data-stick-loading` — button label while in-flight
|
|
156
|
+
- `data-stick-headers` — JSON string of fetch headers
|
|
157
|
+
- `data-stick-json` — JSON body template with interpolation (auto-sets Content-Type)
|
|
158
|
+
- `data-stick-error` — element to display fetch errors
|
|
159
|
+
|
|
160
|
+
### Removed
|
|
161
|
+
- jQuery dependency
|
|
162
|
+
- Old `$.handler:param` syntax
|
|
163
|
+
- `build/` directory (Ant/Java build tools)
|
|
164
|
+
- All HTML5 Boilerplate scaffolding
|
|
165
|
+
|
|
166
|
+
## [1.x] — 2011
|
|
167
|
+
|
|
168
|
+
Original experimental release. jQuery-based.
|
|
169
|
+
Syntax: `data-stick="$.handler:param"`. Handlers: alert, confirm, log, validate.
|
package/README.md
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# Stick.js
|
|
2
|
+
|
|
3
|
+
**Declarative behavior for HTML elements.** Zero dependencies, ~220 lines of vanilla JS.
|
|
4
|
+
|
|
5
|
+
Drop in one `<script>` and annotate your HTML — no JavaScript glue required for the most common UI interactions.
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<script src="stick.js"></script>
|
|
9
|
+
|
|
10
|
+
<button data-stick="click:toggle" data-stick-target="#menu">Menu</button>
|
|
11
|
+
<input data-stick="input:fetch:/api/search?q={{value}}"
|
|
12
|
+
data-stick-target="#results" data-stick-debounce="300">
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
**Script tag** (put before your custom-handler `<script>`):
|
|
20
|
+
```html
|
|
21
|
+
<script src="stick.js"></script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**npm:**
|
|
25
|
+
```bash
|
|
26
|
+
npm install stickjs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**ESM / CommonJS:**
|
|
30
|
+
```js
|
|
31
|
+
import Stick from 'stickjs';
|
|
32
|
+
const Stick = require('stickjs');
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Syntax
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
data-stick="event:handler:param"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Attribute | Purpose |
|
|
44
|
+
|-----------|---------|
|
|
45
|
+
| `data-stick="event:handler:param"` | Primary behavior |
|
|
46
|
+
| `data-stick-2="event:handler:param"` | Stack a 2nd behavior (up to `data-stick-5`) |
|
|
47
|
+
| `data-stick-target="#sel"` | Apply handler to another element |
|
|
48
|
+
| `data-stick-target-2="#other"` | Per-slot target for `data-stick-2` (also `-3`, `-4`, `-5`) |
|
|
49
|
+
| `data-stick-once` | Remove listener after first fire |
|
|
50
|
+
| `data-stick-debounce="300"` | Debounce handler by N ms |
|
|
51
|
+
| `data-stick-throttle="300"` | Throttle handler by N ms |
|
|
52
|
+
| `data-stick-confirm="message"` | `window.confirm()` gate before handler |
|
|
53
|
+
| `data-stick-key="Enter"` | Only fire when `event.key` matches (comma-separated, e.g. `"Enter,Escape"`) |
|
|
54
|
+
| `data-stick-prevent` | Always call `event.preventDefault()` before handler (e.g. on `<form>`) |
|
|
55
|
+
| `data-stick-stop` | Always call `event.stopPropagation()` before handler |
|
|
56
|
+
| `data-stick-passive` | Add event listener as passive (use for `scroll`/`touchmove`) |
|
|
57
|
+
| `data-stick-capture` | Add event listener in capture phase |
|
|
58
|
+
| `data-stick-delegate=".item"` | Event delegation — listen on `el`, fire only for matching descendant elements. Handler receives the matched child as `el`. |
|
|
59
|
+
| `data-stick-method="POST"` | HTTP method for `fetch` handler |
|
|
60
|
+
| `data-stick-swap="beforeend"` | Fetch insertion mode (default: `innerHTML`). Aliases: `prepend` = `afterbegin`, `append` = `beforeend` |
|
|
61
|
+
| `data-stick-loading="Loading…"` | Button label while fetch is in-flight |
|
|
62
|
+
| `data-stick-headers='{"Authorization":"Bearer TOKEN"}'` | Fetch headers (JSON string) |
|
|
63
|
+
| `data-stick-json='{"key":"{{value}}"}'` | JSON body for fetch POST (auto-sets Content-Type) |
|
|
64
|
+
| `data-stick-error="#el"` | Element to display fetch errors |
|
|
65
|
+
|
|
66
|
+
### Target selectors
|
|
67
|
+
|
|
68
|
+
`data-stick-target` accepts:
|
|
69
|
+
|
|
70
|
+
| Value | Resolves to |
|
|
71
|
+
|-------|------------|
|
|
72
|
+
| `"#id"` or any CSS selector | `document.querySelector(sel)` |
|
|
73
|
+
| `"self"` | The trigger element itself (explicit) |
|
|
74
|
+
| `"siblings"` | All sibling elements (same parent, excluding el) |
|
|
75
|
+
| `"all:.cls"` | All elements matching a CSS selector (`querySelectorAll`) |
|
|
76
|
+
| `"next"` | `el.nextElementSibling` |
|
|
77
|
+
| `"prev"` | `el.previousElementSibling` |
|
|
78
|
+
| `"parent"` | `el.parentElement` |
|
|
79
|
+
| `"closest:form"` | `el.closest("form")` |
|
|
80
|
+
|
|
81
|
+
### Param interpolation
|
|
82
|
+
|
|
83
|
+
Tokens in `param` are resolved at event time from the **trigger element**:
|
|
84
|
+
|
|
85
|
+
| Token | Value |
|
|
86
|
+
|-------|-------|
|
|
87
|
+
| `{{value}}` | `el.value` |
|
|
88
|
+
| `{{text}}` | `el.textContent` |
|
|
89
|
+
| `{{id}}` | `el.id` |
|
|
90
|
+
| `{{name}}` | `el.name` |
|
|
91
|
+
| `{{checked}}` | `"true"` or `"false"` |
|
|
92
|
+
| `{{index}}` | Position among parent's children (0-based) |
|
|
93
|
+
| `{{length}}` | `el.children.length` |
|
|
94
|
+
| `{{chars}}` | `el.value.length` or `textContent.length` — character count |
|
|
95
|
+
| `{{data-foo}}` | `el.dataset.foo` |
|
|
96
|
+
| `{{url:key}}` | `URLSearchParams` value from the current page URL (e.g. `{{url:q}}` → `?q=value`) |
|
|
97
|
+
| `{{href}}`, `{{src}}`, etc. | Any attribute via `el.getAttribute(name)` |
|
|
98
|
+
|
|
99
|
+
### Synthetic events
|
|
100
|
+
|
|
101
|
+
| Event | Trigger |
|
|
102
|
+
|-------|---------|
|
|
103
|
+
| `ready` | Fires immediately on bind — use to initialize state |
|
|
104
|
+
| `watch` | Fires immediately on bind, then on every attribute mutation of `el` (MutationObserver) |
|
|
105
|
+
| `intersect` | Element enters the viewport (IntersectionObserver) |
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Built-in handlers
|
|
110
|
+
|
|
111
|
+
### Visibility
|
|
112
|
+
|
|
113
|
+
| Handler | Effect |
|
|
114
|
+
|---------|--------|
|
|
115
|
+
| `show` | `target.hidden = false` |
|
|
116
|
+
| `hide` | `target.hidden = true` |
|
|
117
|
+
| `toggle` | Toggle `target.hidden` |
|
|
118
|
+
|
|
119
|
+
### CSS classes
|
|
120
|
+
|
|
121
|
+
| Handler | Effect |
|
|
122
|
+
|---------|--------|
|
|
123
|
+
| `add-class` | `target.classList.add(param)` |
|
|
124
|
+
| `remove-class` | `target.classList.remove(param)` |
|
|
125
|
+
| `toggle-class` | `target.classList.toggle(param)` |
|
|
126
|
+
|
|
127
|
+
### Content
|
|
128
|
+
|
|
129
|
+
| Handler | Effect |
|
|
130
|
+
|---------|--------|
|
|
131
|
+
| `set-text` | `target.textContent = param` |
|
|
132
|
+
| `set-html` | `target.innerHTML = param` |
|
|
133
|
+
| `set-value` | `target.value = param` |
|
|
134
|
+
| `clear` | `target.textContent = ""` |
|
|
135
|
+
|
|
136
|
+
### Attributes & styles
|
|
137
|
+
|
|
138
|
+
| Handler | Effect |
|
|
139
|
+
|---------|--------|
|
|
140
|
+
| `set-attr` | `target.setAttribute(name, val)` — param: `"name:value"` |
|
|
141
|
+
| `remove-attr` | `target.removeAttribute(param)` |
|
|
142
|
+
| `toggle-attr` | Toggle attribute presence — param is attr name (e.g. `"disabled"`, `"open"`) |
|
|
143
|
+
| `set-style` | `target.style[prop] = val` — param: `"property:value"` |
|
|
144
|
+
|
|
145
|
+
### Focus, scroll & selection
|
|
146
|
+
|
|
147
|
+
| Handler | Effect |
|
|
148
|
+
|---------|--------|
|
|
149
|
+
| `focus` | `target.focus()` |
|
|
150
|
+
| `scroll-to` | `target.scrollIntoView({ behavior: 'smooth' })` |
|
|
151
|
+
| `select` | `target.select()` — select all text in an input/textarea |
|
|
152
|
+
|
|
153
|
+
### Clipboard & navigation
|
|
154
|
+
|
|
155
|
+
| Handler | Effect |
|
|
156
|
+
|---------|--------|
|
|
157
|
+
| `copy` | `navigator.clipboard.writeText(param \|\| el.textContent)` |
|
|
158
|
+
| `navigate` | `window.location.href = param` |
|
|
159
|
+
| `open` | `window.open(param, '_blank', 'noopener')` |
|
|
160
|
+
| `back` | `window.history.back()` |
|
|
161
|
+
| `forward` | `window.history.forward()` |
|
|
162
|
+
| `history-push` | `window.history.pushState({}, '', param)` — update URL without reload |
|
|
163
|
+
| `scroll-top` | `window.scrollTo({ top: 0, behavior: 'smooth' })` — back-to-top |
|
|
164
|
+
| `reload` | `window.location.reload()` |
|
|
165
|
+
| `print` | `window.print()` |
|
|
166
|
+
|
|
167
|
+
### Forms
|
|
168
|
+
|
|
169
|
+
| Handler | Effect |
|
|
170
|
+
|---------|--------|
|
|
171
|
+
| `submit` | Submit closest `<form>` |
|
|
172
|
+
| `reset` | Reset closest `<form>` |
|
|
173
|
+
|
|
174
|
+
### Events
|
|
175
|
+
|
|
176
|
+
| Handler | Effect |
|
|
177
|
+
|---------|--------|
|
|
178
|
+
| `prevent` | `event.preventDefault()` |
|
|
179
|
+
| `stop` | `event.stopPropagation()` |
|
|
180
|
+
| `dispatch` / `emit` | `target.dispatchEvent(new CustomEvent(param, { bubbles: true, detail: { source: el } }))` |
|
|
181
|
+
|
|
182
|
+
### DOM
|
|
183
|
+
|
|
184
|
+
| Handler | Effect |
|
|
185
|
+
|---------|--------|
|
|
186
|
+
| `remove` | `target.remove()` |
|
|
187
|
+
| `disable` | `target.disabled = true` |
|
|
188
|
+
| `enable` | `target.disabled = false` |
|
|
189
|
+
|
|
190
|
+
### Checkboxes
|
|
191
|
+
|
|
192
|
+
| Handler | Effect |
|
|
193
|
+
|---------|--------|
|
|
194
|
+
| `check` | `target.checked = true` |
|
|
195
|
+
| `uncheck` | `target.checked = false` |
|
|
196
|
+
| `toggle-check` | Toggle `target.checked` |
|
|
197
|
+
|
|
198
|
+
### Counters
|
|
199
|
+
|
|
200
|
+
| Handler | Effect |
|
|
201
|
+
|---------|--------|
|
|
202
|
+
| `increment` | Add `param` (default `1`) to `target` value or textContent |
|
|
203
|
+
| `decrement` | Subtract `param` (default `1`) from `target` value or textContent |
|
|
204
|
+
|
|
205
|
+
### Data attributes
|
|
206
|
+
|
|
207
|
+
| Handler | Effect |
|
|
208
|
+
|---------|--------|
|
|
209
|
+
| `set-data` | `target.dataset[key] = val` — param: `"key:value"` |
|
|
210
|
+
|
|
211
|
+
### Native dialog
|
|
212
|
+
|
|
213
|
+
| Handler | Effect |
|
|
214
|
+
|---------|--------|
|
|
215
|
+
| `show-modal` | `target.showModal()` — open native `<dialog>` as modal |
|
|
216
|
+
| `close-modal` | `target.close(param?)` — close native `<dialog>` |
|
|
217
|
+
|
|
218
|
+
### Storage
|
|
219
|
+
|
|
220
|
+
| Handler | Effect |
|
|
221
|
+
|---------|--------|
|
|
222
|
+
| `store` | `localStorage.setItem(key, val)` — param: `"key:value"` |
|
|
223
|
+
| `restore` | `localStorage.getItem(param)` → set target `.value` or `.textContent` |
|
|
224
|
+
|
|
225
|
+
### Templates & animation
|
|
226
|
+
|
|
227
|
+
| Handler | Effect |
|
|
228
|
+
|---------|--------|
|
|
229
|
+
| `clone-template` | Clone `<template>` matched by param selector, resolve `{{tokens}}` from `el`, append to target. Auto-binds new elements. |
|
|
230
|
+
| `animate` | `target.classList.add(param)` — removes the class automatically after `animationend` |
|
|
231
|
+
| `sort` | Sort `target.children` by textContent (default) or param key (e.g. `"data-price"`) — numeric-aware |
|
|
232
|
+
| `count` | Set `target.textContent` to count of elements matching CSS param, or `target.children.length` when empty |
|
|
233
|
+
|
|
234
|
+
### Network
|
|
235
|
+
|
|
236
|
+
| Handler | Notes |
|
|
237
|
+
|---------|-------|
|
|
238
|
+
| `fetch` | Fetch `param` URL, inject response HTML into `target`. Supports `data-stick-method`, `data-stick-swap`, `data-stick-loading`, `data-stick-headers`, `data-stick-json`, `data-stick-error`. |
|
|
239
|
+
|
|
240
|
+
### Debug
|
|
241
|
+
|
|
242
|
+
| Handler | Effect |
|
|
243
|
+
|---------|--------|
|
|
244
|
+
| `log` | `console.log('[Stick]', param)` |
|
|
245
|
+
| `alert` | `window.alert(param)` |
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Examples
|
|
250
|
+
|
|
251
|
+
```html
|
|
252
|
+
<!-- Toggle menu open/closed -->
|
|
253
|
+
<button data-stick="click:toggle-class:open" data-stick-target="#menu">Menu</button>
|
|
254
|
+
|
|
255
|
+
<!-- Show panel on click, only once -->
|
|
256
|
+
<button data-stick="click:show" data-stick-target="#welcome" data-stick-once>
|
|
257
|
+
Show welcome
|
|
258
|
+
</button>
|
|
259
|
+
|
|
260
|
+
<!-- Search with debounce -->
|
|
261
|
+
<input data-stick="input:fetch:/api/search?q={{value}}"
|
|
262
|
+
data-stick-target="#results"
|
|
263
|
+
data-stick-debounce="300"
|
|
264
|
+
placeholder="Search…">
|
|
265
|
+
|
|
266
|
+
<!-- Fetch + append (load more) -->
|
|
267
|
+
<button data-stick="click:fetch:/api/posts?page={{data-page}}"
|
|
268
|
+
data-stick-target="#list"
|
|
269
|
+
data-stick-swap="beforeend"
|
|
270
|
+
data-stick-loading="Loading…"
|
|
271
|
+
data-page="2">Load more</button>
|
|
272
|
+
|
|
273
|
+
<!-- Confirm before destructive action -->
|
|
274
|
+
<button data-stick="click:fetch:/api/item/42"
|
|
275
|
+
data-stick-method="DELETE"
|
|
276
|
+
data-stick-target="parent"
|
|
277
|
+
data-stick-confirm="Delete this item?">Delete</button>
|
|
278
|
+
|
|
279
|
+
<!-- POST JSON -->
|
|
280
|
+
<button data-stick="click:fetch:/api/users"
|
|
281
|
+
data-stick-method="POST"
|
|
282
|
+
data-stick-json='{"name":"{{value}}"}'
|
|
283
|
+
data-stick-target="#result">Create user</button>
|
|
284
|
+
|
|
285
|
+
<!-- Lazy-load on scroll into view -->
|
|
286
|
+
<section data-stick="intersect:fetch:/api/widget"
|
|
287
|
+
data-stick-target="#widget-output"
|
|
288
|
+
data-stick-once>Loading widget…</section>
|
|
289
|
+
|
|
290
|
+
<!-- Stack behaviors with different targets -->
|
|
291
|
+
<button
|
|
292
|
+
data-stick="click:add-class:loading" data-stick-target="#submit-btn"
|
|
293
|
+
data-stick-2="click:show" data-stick-target-2="#spinner"
|
|
294
|
+
data-stick-3="click:fetch:/api/save" data-stick-target-3="#response">
|
|
295
|
+
Save
|
|
296
|
+
</button>
|
|
297
|
+
|
|
298
|
+
<!-- Dispatch event for other components to react -->
|
|
299
|
+
<li data-stick="click:emit:item-selected" data-id="42">Item 42</li>
|
|
300
|
+
|
|
301
|
+
<!-- Navigate to sibling (no ID needed) -->
|
|
302
|
+
<button data-stick="click:toggle" data-stick-target="next">Toggle next</button>
|
|
303
|
+
<div>This toggles</div>
|
|
304
|
+
|
|
305
|
+
<!-- Native dialog modal -->
|
|
306
|
+
<button data-stick="click:show-modal:" data-stick-target="#my-dialog">Open</button>
|
|
307
|
+
<dialog id="my-dialog">
|
|
308
|
+
<p>Hello from dialog</p>
|
|
309
|
+
<button data-stick="click:close-modal:" data-stick-target="closest:dialog">Close</button>
|
|
310
|
+
</dialog>
|
|
311
|
+
|
|
312
|
+
<!-- Submit form on Enter key -->
|
|
313
|
+
<input data-stick="keydown:submit:" data-stick-key="Enter">
|
|
314
|
+
|
|
315
|
+
<!-- Increment / decrement counter -->
|
|
316
|
+
<button data-stick="click:decrement:" data-stick-target="#count">−</button>
|
|
317
|
+
<span id="count">0</span>
|
|
318
|
+
<button data-stick="click:increment:" data-stick-target="#count">+</button>
|
|
319
|
+
|
|
320
|
+
<!-- Tab interface — no JS required -->
|
|
321
|
+
<nav>
|
|
322
|
+
<button class="tab active"
|
|
323
|
+
data-stick="click:add-class:active"
|
|
324
|
+
data-stick-2="click:remove-class:active" data-stick-target-2="siblings">Tab 1</button>
|
|
325
|
+
<button class="tab"
|
|
326
|
+
data-stick="click:add-class:active"
|
|
327
|
+
data-stick-2="click:remove-class:active" data-stick-target-2="siblings">Tab 2</button>
|
|
328
|
+
</nav>
|
|
329
|
+
|
|
330
|
+
<!-- Restore stored preference on page load -->
|
|
331
|
+
<select data-stick="ready:restore:theme-pref" data-stick-target="self"
|
|
332
|
+
data-stick-2="change:store:theme-pref:{{value}}">
|
|
333
|
+
<option>light</option><option>dark</option>
|
|
334
|
+
</select>
|
|
335
|
+
|
|
336
|
+
<!-- Animate on click -->
|
|
337
|
+
<button data-stick="click:animate:bounce" data-stick-target="#icon">Bounce</button>
|
|
338
|
+
<span id="icon">🎉</span>
|
|
339
|
+
|
|
340
|
+
<!-- clone-template with interpolation — add items from input, no JS needed -->
|
|
341
|
+
<template id="task-tpl">
|
|
342
|
+
<li>{{value}} <button data-stick="click:remove" data-stick-target="parent">×</button></li>
|
|
343
|
+
</template>
|
|
344
|
+
<input id="task-input" placeholder="New task"
|
|
345
|
+
data-stick="keydown:clone-template:#task-tpl"
|
|
346
|
+
data-stick-key="Enter"
|
|
347
|
+
data-stick-target="#task-list"
|
|
348
|
+
data-stick-2="keydown:set-value:"
|
|
349
|
+
data-stick-target-2="self">
|
|
350
|
+
<ul id="task-list"></ul>
|
|
351
|
+
|
|
352
|
+
<!-- Sort a list alphabetically -->
|
|
353
|
+
<button data-stick="click:sort" data-stick-target="#task-list">Sort A–Z</button>
|
|
354
|
+
|
|
355
|
+
<!-- Count items -->
|
|
356
|
+
<span data-stick="ready:count:.task" data-stick-target="self"></span>
|
|
357
|
+
|
|
358
|
+
<!-- Event delegation — single listener handles dynamic list items -->
|
|
359
|
+
<ul data-stick="click:remove" data-stick-target="parent" data-stick-delegate=".item">
|
|
360
|
+
<li class="item">Task A <button>×</button></li>
|
|
361
|
+
<li class="item">Task B <button>×</button></li>
|
|
362
|
+
</ul>
|
|
363
|
+
|
|
364
|
+
<!-- Back to top -->
|
|
365
|
+
<button data-stick="click:scroll-top">Back to top</button>
|
|
366
|
+
|
|
367
|
+
<!-- Pre-fill search from URL ?q= param -->
|
|
368
|
+
<input data-stick="ready:set-value:{{url:q}}" data-stick-target="self">
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## JavaScript API
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
Stick.version // "2.9.0"
|
|
377
|
+
|
|
378
|
+
Stick.add(name, fn) // register a handler — chainable
|
|
379
|
+
Stick.remove(name) // unregister a handler — chainable
|
|
380
|
+
Stick.use(plugin) // register a plugin — chainable
|
|
381
|
+
Stick.debug(true?) // enable verbose console logging — chainable
|
|
382
|
+
// plugin: fn(stick) | { install(stick) } | { 'name': fn, … }
|
|
383
|
+
Stick.unbind(el) // remove all listeners from el, clear data-stick-bound — chainable
|
|
384
|
+
// fn signature: (el, param, event, target) => void | Promise<void>
|
|
385
|
+
// el — trigger element (has data-stick)
|
|
386
|
+
// param — string after second colon, {{tokens}} already resolved
|
|
387
|
+
// event — DOM Event or synthetic { type: 'intersect', entry }
|
|
388
|
+
// target — resolved data-stick-target element, or el
|
|
389
|
+
|
|
390
|
+
Stick.handlers // frozen snapshot: { name: fn, … }
|
|
391
|
+
|
|
392
|
+
Stick.init(root?) // scan and bind [data-stick] under root — chainable
|
|
393
|
+
// default: document
|
|
394
|
+
|
|
395
|
+
Stick.observe(root?) // MutationObserver: auto-bind new [data-stick] elements
|
|
396
|
+
// called automatically on load; default: document.body
|
|
397
|
+
|
|
398
|
+
Stick.parse(value) // parse "event:handler:param" → { event, handler, param } | null
|
|
399
|
+
|
|
400
|
+
Stick.bind(el) // bind a single element
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Rebinding
|
|
404
|
+
|
|
405
|
+
Elements are marked `data-stick-bound` after binding to prevent double-bind. Remove it to force rebind:
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
el.removeAttribute('data-stick-bound');
|
|
409
|
+
Stick.bind(el);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Script placement
|
|
413
|
+
|
|
414
|
+
```html
|
|
415
|
+
<script src="stick.js"></script> ← here
|
|
416
|
+
<script>
|
|
417
|
+
Stick.add('my-handler', fn); ← register before DOMContentLoaded fires
|
|
418
|
+
</script>
|
|
419
|
+
<!-- data-stick elements anywhere in body -->
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`stick.js` queues `Stick.init()` on `DOMContentLoaded`, so any `Stick.add()` calls in subsequent `<script>` tags are picked up automatically.
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Testing
|
|
427
|
+
|
|
428
|
+
Open `test/stick.test.html` in a browser. Covers 55+ assertions across all handlers, modifiers, and edge cases.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## LLM-friendly
|
|
433
|
+
|
|
434
|
+
See `llms.txt` for a machine-readable reference optimised for LLM context windows.
|
|
435
|
+
|
|
436
|
+
| Intent | HTML |
|
|
437
|
+
|--------|------|
|
|
438
|
+
| "toggle sidebar on click" | `data-stick="click:toggle" data-stick-target="#sidebar"` |
|
|
439
|
+
| "search as you type, wait 300ms" | `data-stick="input:fetch:/api/search?q={{value}}" data-stick-debounce="300" data-stick-target="#results"` |
|
|
440
|
+
| "confirm before delete" | `data-stick="click:fetch:/api/items/1" data-stick-method="DELETE" data-stick-confirm="Delete?" data-stick-target="parent"` |
|
|
441
|
+
| "lazy-load when visible" | `data-stick="intersect:fetch:/api/widget" data-stick-once data-stick-target="#out"` |
|
|
442
|
+
| "copy link on click" | `data-stick="click:copy:https://example.com"` |
|
|
443
|
+
| "two behaviors, two targets" | `data-stick="click:show" data-stick-target="#modal" data-stick-2="click:add-class:open" data-stick-target-2="#overlay"` |
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## License
|
|
448
|
+
|
|
449
|
+
GPL-3.0 © 2011–2026 Kaique Silva
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": {
|
|
3
|
+
"accordion": { "html": "components/accordion.html", "plugin": null },
|
|
4
|
+
"tabs": { "html": "components/tabs.html", "plugin": null },
|
|
5
|
+
"toggle": { "html": "components/toggle.html", "plugin": null },
|
|
6
|
+
"notification": { "html": "components/notification.html", "plugin": null },
|
|
7
|
+
"copy-button": { "html": "components/copy-button.html", "plugin": null },
|
|
8
|
+
"skeleton": { "html": "components/skeleton.html", "plugin": null },
|
|
9
|
+
"dialog": { "html": "components/dialog.html", "plugin": null },
|
|
10
|
+
"toast": { "html": "components/toast.html", "plugin": "plugins/toast.js" },
|
|
11
|
+
"dropdown": { "html": "components/dropdown.html", "plugin": "plugins/dropdown.js" },
|
|
12
|
+
"tooltip": { "html": "components/tooltip.html", "plugin": "plugins/tooltip.js" },
|
|
13
|
+
"toggle-group": { "html": "components/toggle-group.html", "plugin": null },
|
|
14
|
+
"command-palette": { "html": "components/command-palette.html", "plugin": "plugins/command-palette.js" },
|
|
15
|
+
"data-table": { "html": "components/data-table.html", "plugin": "plugins/data-table.js" },
|
|
16
|
+
"stepper": { "html": "components/stepper.html", "plugin": "plugins/stepper.js" },
|
|
17
|
+
"autocomplete": { "html": "components/autocomplete.html", "plugin": "plugins/autocomplete.js" }
|
|
18
|
+
},
|
|
19
|
+
"css": "stick-ui.css"
|
|
20
|
+
}
|