@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/bin/stickjs.js ADDED
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ // ── Registry ────────────────────────────────────────────────────────────────
9
+ const registry = require('./registry.json');
10
+ const COMPONENTS = registry.components;
11
+ const NAMES = Object.keys(COMPONENTS);
12
+
13
+ // ── Paths ───────────────────────────────────────────────────────────────────
14
+ const PKG_ROOT = path.join(__dirname, '..', 'stick-ui'); // source in npm package
15
+ const DEST = path.resolve(process.cwd(), 'stick-ui'); // user's project
16
+
17
+ // ── ANSI helpers ────────────────────────────────────────────────────────────
18
+ const RESET = '\x1b[0m';
19
+ const BOLD = '\x1b[1m';
20
+ const DIM = '\x1b[2m';
21
+ const GREEN = '\x1b[32m';
22
+ const CYAN = '\x1b[36m';
23
+ const YELLOW = '\x1b[33m';
24
+ const RED = '\x1b[31m';
25
+
26
+ function green(s) { return GREEN + s + RESET; }
27
+ function cyan(s) { return CYAN + s + RESET; }
28
+ function yellow(s) { return YELLOW + s + RESET; }
29
+ function red(s) { return RED + s + RESET; }
30
+ function dim(s) { return DIM + s + RESET; }
31
+ function bold(s) { return BOLD + s + RESET; }
32
+
33
+ // ── Helpers ─────────────────────────────────────────────────────────────────
34
+ function copyFile(src, dest) {
35
+ const dir = path.dirname(dest);
36
+ if (!fs.existsSync(dir)) {
37
+ fs.mkdirSync(dir, { recursive: true });
38
+ }
39
+ fs.copyFileSync(src, dest);
40
+ }
41
+
42
+ // ── Commands ────────────────────────────────────────────────────────────────
43
+
44
+ function showHelp() {
45
+ console.log(`
46
+ ${bold('stickjs')} — CLI for Stick.js UI components
47
+
48
+ ${bold('Usage:')}
49
+ npx stickjs ${cyan('list')} List available components
50
+ npx stickjs ${cyan('add')} <name> [name...] Add component(s) to ./stick-ui/
51
+ npx stickjs ${cyan('add')} ${dim('--all')} Add all components
52
+ npx stickjs ${cyan('add')} <name> ${dim('--force')} Overwrite existing files
53
+
54
+ ${bold('Options:')}
55
+ ${dim('--help, -h')} Show this help message
56
+ ${dim('--all')} Add every component
57
+ ${dim('--force')} Overwrite existing files without warning
58
+
59
+ ${bold('Examples:')}
60
+ npx stickjs add dialog toast
61
+ npx stickjs add --all --force
62
+ `);
63
+ }
64
+
65
+ function listComponents() {
66
+ console.log(`\n${bold('Available components')} ${dim('(' + NAMES.length + ')')}\n`);
67
+ const maxLen = Math.max(...NAMES.map(n => n.length));
68
+ for (const name of NAMES) {
69
+ const entry = COMPONENTS[name];
70
+ const plugin = entry.plugin ? yellow(' + plugin') : '';
71
+ console.log(` ${cyan(name.padEnd(maxLen))} ${dim(entry.html)}${plugin}`);
72
+ }
73
+ console.log();
74
+ }
75
+
76
+ function addComponents(args) {
77
+ const force = args.includes('--force');
78
+ const all = args.includes('--all');
79
+ const names = all
80
+ ? NAMES
81
+ : args.filter(a => !a.startsWith('--'));
82
+
83
+ if (names.length === 0) {
84
+ console.log(red('\nError: specify at least one component name, or use --all\n'));
85
+ console.log(`Run ${cyan('npx stickjs list')} to see available components.\n`);
86
+ process.exit(1);
87
+ }
88
+
89
+ // Validate names
90
+ const invalid = names.filter(n => !COMPONENTS[n]);
91
+ if (invalid.length > 0) {
92
+ console.log(red('\nUnknown component(s): ') + invalid.join(', '));
93
+ console.log(`Run ${cyan('npx stickjs list')} to see available components.\n`);
94
+ process.exit(1);
95
+ }
96
+
97
+ let added = 0;
98
+ let skipped = 0;
99
+
100
+ // Copy CSS on first add (if not present)
101
+ const cssSrc = path.join(PKG_ROOT, registry.css);
102
+ const cssDest = path.join(DEST, registry.css);
103
+ if (!fs.existsSync(cssDest)) {
104
+ copyFile(cssSrc, cssDest);
105
+ console.log(green(' + ') + dim('stick-ui.css'));
106
+ }
107
+
108
+ for (const name of names) {
109
+ const entry = COMPONENTS[name];
110
+
111
+ // HTML file
112
+ const htmlSrc = path.join(PKG_ROOT, entry.html);
113
+ const htmlDest = path.join(DEST, name + '.html');
114
+
115
+ if (fs.existsSync(htmlDest) && !force) {
116
+ console.log(yellow(' ~ ') + name + '.html' + dim(' (exists, use --force to overwrite)'));
117
+ skipped++;
118
+ continue;
119
+ }
120
+
121
+ copyFile(htmlSrc, htmlDest);
122
+ console.log(green(' + ') + name + '.html');
123
+
124
+ // Plugin file
125
+ if (entry.plugin) {
126
+ const pluginSrc = path.join(PKG_ROOT, entry.plugin);
127
+ const pluginDest = path.join(DEST, 'plugins', path.basename(entry.plugin));
128
+
129
+ if (fs.existsSync(pluginDest) && !force) {
130
+ console.log(yellow(' ~ ') + 'plugins/' + path.basename(entry.plugin) + dim(' (exists, use --force)'));
131
+ skipped++;
132
+ } else {
133
+ copyFile(pluginSrc, pluginDest);
134
+ console.log(green(' + ') + 'plugins/' + path.basename(entry.plugin));
135
+ }
136
+ }
137
+
138
+ added++;
139
+ }
140
+
141
+ console.log(`\n${bold('Done:')} ${green(added + ' added')}, ${yellow(skipped + ' skipped')}\n`);
142
+ }
143
+
144
+ // ── Main ────────────────────────────────────────────────────────────────────
145
+ const args = process.argv.slice(2);
146
+ const command = args[0];
147
+
148
+ if (!command || command === '--help' || command === '-h') {
149
+ showHelp();
150
+ } else if (command === 'list') {
151
+ listComponents();
152
+ } else if (command === 'add') {
153
+ addComponents(args.slice(1));
154
+ } else {
155
+ console.log(red(`\nUnknown command: ${command}`));
156
+ console.log(`Run ${cyan('npx stickjs --help')} for usage.\n`);
157
+ process.exit(1);
158
+ }
package/llms.txt ADDED
@@ -0,0 +1,244 @@
1
+ # Stick.js
2
+
3
+ > Declarative behavior for HTML elements via data attributes. Zero dependencies, ~200 lines of vanilla JS.
4
+
5
+ ## Core concept
6
+
7
+ Annotate any HTML element with `data-stick="event:handler:param"` to attach behavior without writing JavaScript glue code.
8
+
9
+ ```html
10
+ <script src="stick.js"></script>
11
+
12
+ <button data-stick="click:alert:Hello!">Click me</button>
13
+ <input data-stick="input:set-text:{{value}}" data-stick-target="#output">
14
+ <div id="output">mirrored here</div>
15
+ ```
16
+
17
+ ## Full syntax
18
+
19
+ ```
20
+ data-stick="event:handler:param" (trailing colon optional when param is empty — "click:toggle" works)
21
+ data-stick="event:handler" — shorthand, same as above
22
+ data-stick-delegate=".sel" — event delegation: listen on el, fire only for matching descendants
23
+ data-stick-target="selector" — apply to another element
24
+ data-stick-2="event:handler:param" — stack a second behavior (up to data-stick-5)
25
+ data-stick-target-2="#other" — per-slot target for data-stick-2 (also -3, -4, -5)
26
+ data-stick-once — remove listener after first fire
27
+ data-stick-debounce="300" — debounce N ms
28
+ data-stick-throttle="300" — throttle N ms
29
+ data-stick-confirm="message" — require window.confirm() before running
30
+ data-stick-key="Enter" — only fire on matching KeyboardEvent.key (comma-separated)
31
+ data-stick-prevent — always call event.preventDefault() before handler
32
+ data-stick-stop — always call event.stopPropagation() before handler
33
+ data-stick-passive — addEventListener passive:true (scroll/touch perf)
34
+ data-stick-capture — addEventListener capture:true
35
+ data-stick-method="POST" — HTTP method for fetch handler
36
+ data-stick-swap="beforeend" — fetch insertion mode (innerHTML|outerHTML|prepend|append|beforeend|afterbegin|…)
37
+ data-stick-loading="Loading…" — button text while fetch is in-flight
38
+ data-stick-headers='{"Authorization":"Bearer TOKEN"}' — fetch headers (JSON string)
39
+ data-stick-json='{"key":"{{value}}"}' — JSON body for fetch POST
40
+ data-stick-error="#error-el" — element to show fetch errors in
41
+ data-stick-group="name" — mutual exclusivity: show/toggle/show-modal hides others in same group
42
+ data-stick-transition="name" — CSS enter/leave transitions: adds name-enter-active / name-leave-active classes with automatic cleanup
43
+ ```
44
+
45
+ ## Target selectors (data-stick-target)
46
+
47
+ ```
48
+ "#id" querySelector (default)
49
+ "self" el itself (explicit)
50
+ "siblings" all sibling elements (same parent, excluding el) — enables tab patterns
51
+ "all:.class" all elements matching the CSS selector (querySelectorAll)
52
+ "next" el.nextElementSibling
53
+ "prev" el.previousElementSibling
54
+ "parent" el.parentElement
55
+ "closest:form" el.closest("form")
56
+ ```
57
+
58
+ ## Param interpolation (resolved at event time)
59
+
60
+ ```
61
+ {{value}} el.value
62
+ {{text}} el.textContent
63
+ {{id}} el.id
64
+ {{name}} el.name
65
+ {{checked}} el.checked → "true"/"false"
66
+ {{index}} el's position among parent.children (0-based)
67
+ {{length}} el.children.length
68
+ {{chars}} el.value.length (or textContent.length) — for character counters
69
+ {{url:key}} URLSearchParams value from current page URL (e.g. {{url:q}} for ?q=)
70
+ {{data-foo}} el.dataset.foo
71
+ {{href}} el.getAttribute("href") — any attribute name works
72
+ ```
73
+
74
+ ## All built-in handlers
75
+
76
+ | handler | description |
77
+ |----------------|------------------------------------------------------|
78
+ | show | target.hidden = false; auto-manages aria-expanded on trigger |
79
+ | hide | target.hidden = true; auto-manages aria-expanded on trigger |
80
+ | toggle | toggle target.hidden; auto-manages aria-expanded on trigger |
81
+ | add-class | target.classList.add(param) |
82
+ | remove-class | target.classList.remove(param) |
83
+ | toggle-class | target.classList.toggle(param) |
84
+ | set-text | target.textContent = param |
85
+ | set-html | target.innerHTML = param |
86
+ | set-value | target.value = param |
87
+ | clear | target.textContent = "" |
88
+ | set-attr | target.setAttribute(name, value) — param: "name:val" |
89
+ | set-style | target.style[prop] = value — param: "property:value" |
90
+ | remove-attr | target.removeAttribute(param) |
91
+ | toggle-attr | toggle attribute presence on target (e.g. "disabled", "open") |
92
+ | clone-template | clone &lt;template&gt; matched by param; resolves {{tokens}} from el; appends to target |
93
+ | sort | sort target.children by textContent (default) or param key (e.g. "data-price") |
94
+ | count | set target.textContent to count of querySelectorAll(param) or target.children.length |
95
+ | animate | target.classList.add(param); auto-removes class after animationend |
96
+ | focus | target.focus() |
97
+ | scroll-to | target.scrollIntoView({ behavior: "smooth" }) |
98
+ | copy | navigator.clipboard.writeText(param or el.textContent)|
99
+ | navigate | window.location.href = param |
100
+ | open | window.open(param, '_blank', 'noopener') |
101
+ | back | window.history.back() |
102
+ | forward | window.history.forward() |
103
+ | history-push | window.history.pushState({}, '', param) — update URL without reload |
104
+ | scroll-top | window.scrollTo({ top: 0, behavior: 'smooth' }) — back-to-top |
105
+ | select | target.select() — select all text in input/textarea |
106
+ | show-modal | target.showModal() — native &lt;dialog&gt; |
107
+ | close-modal | target.close(param?) — native &lt;dialog&gt; |
108
+ | submit | closest form submit |
109
+ | reset | closest form reset |
110
+ | prevent | event.preventDefault() |
111
+ | stop | event.stopPropagation() |
112
+ | remove | target.remove() |
113
+ | disable | target.disabled = true |
114
+ | enable | target.disabled = false |
115
+ | reload | window.location.reload() |
116
+ | print | window.print() |
117
+ | log | console.log(param) |
118
+ | alert | window.alert(param) |
119
+ | dispatch | target.dispatchEvent(new CustomEvent(param, { bubbles: true, detail: { source: el } })) |
120
+ | emit | alias for dispatch |
121
+ | store | localStorage.setItem(key, value) — param: "key:value"|
122
+ | restore | localStorage.getItem(param) → set target value/text |
123
+ | fetch | fetch(param) → inject response into target |
124
+ | check | target.checked = true |
125
+ | uncheck | target.checked = false |
126
+ | toggle-check | toggle target.checked |
127
+ | set-data | target.dataset[key] = value — param: "key:value" |
128
+ | increment | target value/textContent += step (default 1) |
129
+ | decrement | target value/textContent -= step (default 1) |
130
+ | set-aria | set ARIA attribute — param: "name:value" (auto-prefixes aria-) |
131
+ | toggle-aria | flip ARIA boolean attribute true↔false — param: attribute name (auto-prefixes aria-) |
132
+ | intersect | fires via IntersectionObserver when el enters viewport |
133
+
134
+ ## Error boundary
135
+
136
+ All handler invocations are wrapped in try/catch. A failing handler is logged to console.error but does not prevent sibling handlers on the same element from running.
137
+
138
+ ## Synthetic events
139
+
140
+ ```
141
+ ready — fires immediately on bind (before any user interaction)
142
+ watch — fires immediately on bind, then on every attribute mutation of el (MutationObserver)
143
+ intersect — IntersectionObserver; fires when element enters viewport
144
+ ```
145
+
146
+ ## JavaScript API
147
+
148
+ ```js
149
+ Stick.version // "3.0.0"
150
+ Stick.add(name, fn) // register handler; fn(el, param, event, target) => void
151
+ Stick.remove(name) // unregister handler
152
+ Stick.use(plugin) // register plugin: fn(stick) | { install } | { name: fn, … }
153
+ Stick.debug(true?) // enable verbose console logging (dev mode)
154
+ Stick.unbind(el) // remove all listeners, clear data-stick-bound (allows rebind)
155
+ Stick.handlers // frozen snapshot of { name: fn, … }
156
+ Stick.init(root?) // scan and bind [data-stick] under root (default: document)
157
+ Stick.observe(root?) // MutationObserver auto-bind (default: document.body)
158
+ Stick.parse(value) // → { event, handler, param } | null
159
+ Stick.fire(el, handler, param?) // invoke handler programmatically (chainable)
160
+ Stick.on(event, fn) // lifecycle hooks: 'bind', 'unbind' (chainable)
161
+ Stick.bind(el) // bind a single element
162
+ ```
163
+
164
+ ## Custom handler pattern
165
+
166
+ ```js
167
+ Stick.add('my-handler', (el, param, event, target) => {
168
+ // el — element with data-stick (the trigger)
169
+ // param — string after second colon, with {{tokens}} already resolved
170
+ // event — DOM Event (or { type: 'intersect', entry } for intersect)
171
+ // target — resolved data-stick-target element, or el if none
172
+ });
173
+ ```
174
+
175
+ ## LLM usage patterns
176
+
177
+ When generating HTML with Stick.js, map user intent to attributes:
178
+
179
+ | Intent | HTML |
180
+ |--------|------|
181
+ | "toggle sidebar on click" | `data-stick="click:toggle" data-stick-target="#sidebar"` |
182
+ | "add loading class while fetching" | `data-stick="click:add-class:loading" data-stick-2="click:fetch:/api/data" data-stick-target="#result"` |
183
+ | "search as user types, debounced" | `data-stick="input:fetch:/search?q={{value}}" data-stick-debounce="300" data-stick-target="#results"` |
184
+ | "confirm before delete" | `data-stick="click:fetch:/api/item/1" data-stick-method="DELETE" data-stick-confirm="Delete?" data-stick-target="parent"` |
185
+ | "lazy load on scroll into view" | `data-stick="intersect:fetch:/api/widget" data-stick-target="#widget" data-stick-once` |
186
+ | "copy link on click" | `data-stick="click:copy:https://example.com"` |
187
+ | "submit form via AJAX" | `data-stick="submit:prevent:" data-stick-2="submit:fetch:/api" data-stick-target="#response"` |
188
+ | "dispatch event for other components" | `data-stick="click:dispatch:user-selected" data-stick-target="#app"` |
189
+ | "increment counter on click" | `data-stick="click:increment:" data-stick-target="#count"` |
190
+ | "decrement with custom step" | `data-stick="click:decrement:5" data-stick-target="#count"` |
191
+ | "check all checkboxes in group" | `data-stick="click:check:" data-stick-target=".cb-item"` |
192
+ | "toggle checkbox on click" | `data-stick="click:toggle-check:" data-stick-target="#my-checkbox"` |
193
+ | "tag element with data attribute" | `data-stick="click:set-data:status:active" data-stick-target="#item"` |
194
+ | "passive scroll listener" | `data-stick="scroll:log:scrolled" data-stick-passive` |
195
+ | "submit form on Enter key" | `data-stick="keydown:submit:" data-stick-key="Enter"` |
196
+ | "close modal on Escape key" | `data-stick="keydown:close-modal:" data-stick-key="Escape" data-stick-target="#my-dialog"` |
197
+ | "open link in new tab" | `data-stick="click:open:https://example.com"` |
198
+ | "show native dialog modal" | `data-stick="click:show-modal:" data-stick-target="#my-dialog"` |
199
+ | "close dialog on button" | `data-stick="click:close-modal:" data-stick-target="closest:dialog"` |
200
+ | "go back in history" | `data-stick="click:back:"` |
201
+ | "add row from template" | `data-stick="click:clone-template:#row-tpl" data-stick-target="#list"` |
202
+ | "toggle details/open" | `data-stick="click:toggle-attr:open" data-stick-target="closest:details"` |
203
+ | "submit form (prevent default)" | `data-stick="submit:fetch:/api/save" data-stick-target="#result" data-stick-prevent` |
204
+ | "stop click propagation" | `data-stick="click:toggle-class:active" data-stick-stop` |
205
+ | "tab interface — active tab" | `data-stick="click:add-class:active" data-stick-2="click:remove-class:active" data-stick-target-2="siblings"` |
206
+ | "show one panel, hide siblings" | `data-stick="click:show" data-stick-target="#panel" data-stick-2="click:hide" data-stick-target-2="all:.panel"` |
207
+ | "restore theme preference on load" | `data-stick="ready:restore:theme" data-stick-target="self"` |
208
+ | "animate element on click" | `data-stick="click:animate:shake" data-stick-target="#icon"` |
209
+ | "character counter" | `data-stick="input:set-text:{{chars}}/280" data-stick-target="#count"` |
210
+ | "dispatch with list index" | `data-stick="click:dispatch:item-click:{{index}}"` |
211
+ | "register a plugin" | `Stick.use({ 'my-handler': (el, p, e, t) => { ... } })` |
212
+ | "unbind element for cleanup" | `Stick.unbind(el)` |
213
+ | "hide all matching elements" | `data-stick="click:hide:" data-stick-target="all:.notification"` |
214
+ | "update URL without reload" | `data-stick="click:history-push:/new-path"` |
215
+ | "select all text on focus" | `data-stick="focus:select" data-stick-target="self"` |
216
+ | "react to data-attribute changes" | `data-stick="watch:set-text:{{data-count}}" data-stick-target="#display"` |
217
+ | "toggle without trailing colon" | `data-stick="click:toggle" data-stick-target="#panel"` |
218
+ | "dynamic list — delete item" | `data-stick="click:remove" data-stick-target="parent" data-stick-delegate=".item"` |
219
+ | "back to top button" | `data-stick="click:scroll-top"` |
220
+ | "pre-fill input from URL param" | `data-stick="ready:set-value:{{url:q}}" data-stick-target="self"` |
221
+ | "remove item by id (delegation)" | `data-stick="click:fetch:/api/item/{{data-id}}" data-stick-method="DELETE" data-stick-delegate="[data-id]" data-stick-target="parent"` |
222
+ | "set aria-expanded on click" | `data-stick="click:set-aria:expanded:true" data-stick-target="#menu"` |
223
+ | "toggle aria-checked" | `data-stick="click:toggle-aria:checked" data-stick-target="self"` |
224
+ | "exclusive accordion panels" | `data-stick="click:show" data-stick-target="#panel1" data-stick-group="accordion"` |
225
+ | "fade-in transition on show" | `data-stick="click:show" data-stick-target="#box" data-stick-transition="fade"` |
226
+ | "fire handler programmatically" | `Stick.fire(el, 'toggle-class', 'active')` |
227
+ | "listen for bind events" | `Stick.on('bind', el => console.log('bound', el))` |
228
+ | "add task from input on Enter" | `data-stick="keydown:clone-template:#task-tpl" data-stick-key="Enter" data-stick-target="#list"` (template uses {{value}}) |
229
+ | "sort list alphabetically" | `data-stick="click:sort" data-stick-target="#list"` |
230
+ | "sort by data attribute" | `data-stick="click:sort:data-price" data-stick-target="#list"` |
231
+ | "count matching items" | `data-stick="click:count:.item" data-stick-target="#count"` |
232
+ | "fetch and prepend results" | `data-stick="click:fetch:/api/more" data-stick-swap="prepend" data-stick-target="#list"` |
233
+
234
+ ## Install
235
+
236
+ ```html
237
+ <script src="stick.js"></script>
238
+ ```
239
+
240
+ Script tag placement: put `<script src="stick.js">` BEFORE any `<script>` that registers custom handlers with `Stick.add()`, so handlers are registered before the auto-init `DOMContentLoaded` fires.
241
+
242
+ ```
243
+ npm install stickjs
244
+ ```
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@ktfth/stickjs",
3
+ "version": "3.0.0",
4
+ "description": "Declarative behavior for HTML elements. Zero dependencies.",
5
+ "main": "stick.js",
6
+ "types": "stick.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "require": "./stick.js",
10
+ "import": "./stick.js",
11
+ "default": "./stick.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "stickjs": "./bin/stickjs.js"
16
+ },
17
+ "files": [
18
+ "stick.js",
19
+ "stick.d.ts",
20
+ "llms.txt",
21
+ "README.md",
22
+ "CHANGELOG.md",
23
+ "bin/",
24
+ "stick-ui/"
25
+ ],
26
+ "scripts": {
27
+ "minify": "npx esbuild stick.js --bundle --minify --outfile=stick.min.js --platform=browser",
28
+ "test": "echo 'Open test/stick.test.html in a browser to run tests'",
29
+ "release": "npm run minify && npm publish --access=public && vercel --prod ./docs"
30
+ },
31
+ "keywords": ["declarative", "html", "behavior", "htmx", "vanilla", "framework", "no-js", "components", "ui", "cli"],
32
+ "author": "Kaique Silva <kaique.developer@gmail.com>",
33
+ "license": "GPL-3.0",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/kaiquekandykoga/Stickjs"
37
+ }
38
+ }
@@ -0,0 +1,25 @@
1
+ <!-- Stick UI: Accordion
2
+ Requires: stick.js
3
+ Optional: stick-ui.css (for .stk-* styles)
4
+ For exclusive mode, add data-stick-group="accordion-name" to each content panel.
5
+ -->
6
+ <div class="stk-accordion">
7
+ <div class="stk-accordion-item">
8
+ <button class="stk-accordion-trigger" aria-expanded="false"
9
+ data-stick="click:toggle" data-stick-target="next">
10
+ Section title
11
+ </button>
12
+ <div class="stk-accordion-content" hidden>
13
+ <p>Section content goes here.</p>
14
+ </div>
15
+ </div>
16
+ <div class="stk-accordion-item">
17
+ <button class="stk-accordion-trigger" aria-expanded="false"
18
+ data-stick="click:toggle" data-stick-target="next">
19
+ Another section
20
+ </button>
21
+ <div class="stk-accordion-content" hidden>
22
+ <p>More content here.</p>
23
+ </div>
24
+ </div>
25
+ </div>
@@ -0,0 +1,82 @@
1
+ <!-- Stick UI: Autocomplete
2
+ Requires: stick.js + stick-ui/plugins/autocomplete.js
3
+ Optional: stick-ui.css
4
+ -->
5
+
6
+ <!-- ── Variant 1: Static options (from a hidden <ul>) ─────── -->
7
+
8
+ <div class="stk-autocomplete" style="width: 300px;">
9
+ <label class="stk-label" for="lang-input">Programming Language</label>
10
+ <input id="lang-input"
11
+ class="stk-autocomplete-input"
12
+ type="text"
13
+ placeholder="Search languages…"
14
+ data-stick="input:autocomplete:#language-list"
15
+ data-stk-autocomplete-min="1">
16
+
17
+ <!-- Hidden source list -->
18
+ <ul id="language-list" hidden>
19
+ <li data-value="javascript">JavaScript</li>
20
+ <li data-value="typescript">TypeScript</li>
21
+ <li data-value="python">Python</li>
22
+ <li data-value="rust">Rust</li>
23
+ <li data-value="go">Go</li>
24
+ <li data-value="java">Java</li>
25
+ <li data-value="csharp">C#</li>
26
+ <li data-value="cpp">C++</li>
27
+ <li data-value="ruby">Ruby</li>
28
+ <li data-value="swift">Swift</li>
29
+ <li data-value="kotlin">Kotlin</li>
30
+ <li data-value="php">PHP</li>
31
+ </ul>
32
+ </div>
33
+
34
+ <!-- ── Variant 2: Remote / programmatic ───────────────────── -->
35
+
36
+ <!--
37
+ Remote example: set data-stk-autocomplete-url to your API endpoint.
38
+ The plugin appends the query string, e.g. /api/search?q=reac
39
+ Expects a JSON response: ["React","React Native"] or [{label,value}]
40
+ -->
41
+ <div class="stk-autocomplete" style="width: 300px; margin-top: 24px;">
42
+ <label class="stk-label" for="remote-input">Search Packages</label>
43
+ <input id="remote-input"
44
+ class="stk-autocomplete-input"
45
+ type="text"
46
+ placeholder="Search npm packages…"
47
+ data-stick="input:autocomplete"
48
+ data-stk-autocomplete-url="https://api.example.com/packages?q="
49
+ data-stk-autocomplete-min="2">
50
+ </div>
51
+
52
+ <!--
53
+ Programmatic example: use stkAutocomplete.setOptions() to populate
54
+ options from your own script or after an API call.
55
+ -->
56
+ <div class="stk-autocomplete" style="width: 300px; margin-top: 24px;">
57
+ <label class="stk-label" for="custom-input">City</label>
58
+ <input id="custom-input"
59
+ class="stk-autocomplete-input"
60
+ type="text"
61
+ placeholder="Type a city name…"
62
+ data-stick="input:autocomplete"
63
+ data-stk-autocomplete-min="1">
64
+ </div>
65
+
66
+ <script>
67
+ // After stick.js and autocomplete.js are loaded:
68
+ stkAutocomplete.setOptions(document.getElementById('custom-input'), [
69
+ 'New York',
70
+ 'Los Angeles',
71
+ 'Chicago',
72
+ 'Houston',
73
+ 'Phoenix',
74
+ 'San Francisco',
75
+ 'Seattle',
76
+ 'Denver',
77
+ 'Boston',
78
+ 'Miami',
79
+ 'Austin',
80
+ 'Portland'
81
+ ]);
82
+ </script>
@@ -0,0 +1,28 @@
1
+ <!-- Stick UI: Command Palette
2
+ Requires: stick.js, stick-ui/plugins/command-palette.js
3
+ Optional: stick-ui.css (for styled palette)
4
+ Global shortcut: Ctrl+K (Cmd+K on Mac)
5
+ -->
6
+
7
+ <!-- Button trigger -->
8
+ <button class="stk-btn" data-stick="click:command-palette">
9
+ Open Command Palette
10
+ <span class="stk-command-palette-shortcut" style="margin-left:8px">Ctrl+K</span>
11
+ </button>
12
+
13
+ <!-- Register commands -->
14
+ <script>
15
+ stkCommandPalette.register([
16
+ // Navigation group
17
+ { id: 'nav-home', label: 'Go to Home', group: 'Navigation', shortcut: 'G H', action: function() { console.log('Navigate: Home'); } },
18
+ { id: 'nav-settings', label: 'Go to Settings', group: 'Navigation', shortcut: 'G S', action: function() { console.log('Navigate: Settings'); } },
19
+ { id: 'nav-profile', label: 'Go to Profile', group: 'Navigation', shortcut: 'G P', action: function() { console.log('Navigate: Profile'); } },
20
+ { id: 'nav-docs', label: 'Open Documentation', group: 'Navigation', action: function() { console.log('Navigate: Docs'); } },
21
+
22
+ // Actions group
23
+ { id: 'act-theme', label: 'Toggle Dark Mode', group: 'Actions', shortcut: 'Ctrl+D', action: function() { document.documentElement.toggleAttribute('data-theme'); } },
24
+ { id: 'act-copy', label: 'Copy Current URL', group: 'Actions', shortcut: 'Ctrl+C', action: function() { navigator.clipboard.writeText(location.href); } },
25
+ { id: 'act-reload', label: 'Reload Page', group: 'Actions', shortcut: 'Ctrl+R', action: function() { location.reload(); } },
26
+ { id: 'act-print', label: 'Print Page', group: 'Actions', shortcut: 'Ctrl+P', action: function() { window.print(); } }
27
+ ]);
28
+ </script>
@@ -0,0 +1,12 @@
1
+ <!-- Stick UI: Copy Button
2
+ Requires: stick.js
3
+ Optional: stick-ui.css
4
+ Copies the param text. Shows "Copied!" feedback for 1.5s.
5
+ -->
6
+ <button class="stk-btn stk-btn-sm"
7
+ data-stick="click:copy:Text to copy"
8
+ data-stick-2="click:set-text:Copied!"
9
+ data-stick-3="click:wait:1500"
10
+ data-stick-4="click:set-text:Copy">
11
+ Copy
12
+ </button>