accordionary 1.0.0 → 1.0.2
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 +169 -1
- package/dist/accordionary.cjs.js +1 -1
- package/dist/accordionary.d.ts +5 -2
- package/dist/accordionary.esm.js +1 -1
- package/dist/accordionary.js +1 -1
- package/dist/generate-accordion.d.ts +47 -0
- package/dist/types.d.ts +63 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A lightweight, accessible, vanilla JavaScript accordion with zero dependencies.
|
|
|
11
11
|
- Respects `prefers-reduced-motion`
|
|
12
12
|
- Configurable via HTML attributes
|
|
13
13
|
- Programmatic API for full control
|
|
14
|
+
- Generate accordions from JSON data (package manager only)
|
|
14
15
|
- TypeScript support
|
|
15
16
|
- ~4KB minified
|
|
16
17
|
|
|
@@ -27,7 +28,7 @@ Download `dist/accordionary.js` and include it in your HTML:
|
|
|
27
28
|
Or link directly from GitHub (replace `v1.0.0` with the desired version):
|
|
28
29
|
|
|
29
30
|
```html
|
|
30
|
-
<script src="https://cdn.jsdelivr.net/
|
|
31
|
+
<script src="https://cdn.jsdelivr.net/npm/accordionary@1.0.0/dist/accordionary.js"></script>
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
This version auto-initializes all accordions on page load.
|
|
@@ -127,6 +128,7 @@ All configuration is done via HTML attributes. No JavaScript required.
|
|
|
127
128
|
| `accordionary-multiple` | `true`, `false` | `true` | Allow multiple items open at once |
|
|
128
129
|
| `accordionary-speed` | number (ms) | `300` | Animation duration in milliseconds |
|
|
129
130
|
| `accordionary-easing` | CSS easing | `ease` | Animation easing function |
|
|
131
|
+
| `accordionary-link` | `true`, `false` | `false` | Link all items: open/close together |
|
|
130
132
|
|
|
131
133
|
### Item-Level Attributes
|
|
132
134
|
|
|
@@ -165,6 +167,16 @@ All configuration is done via HTML attributes. No JavaScript required.
|
|
|
165
167
|
</div>
|
|
166
168
|
```
|
|
167
169
|
|
|
170
|
+
### Linked Items (Open/Close Together)
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<div accordionary="component" accordionary-link="true">
|
|
174
|
+
<!-- Clicking any item open opens all; clicking any item closed closes all -->
|
|
175
|
+
</div>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Disabled items are excluded from linking — they remain in their current state.
|
|
179
|
+
|
|
168
180
|
### Force Specific Item Open
|
|
169
181
|
|
|
170
182
|
```html
|
|
@@ -259,6 +271,162 @@ const accordion: AccordionController | null =
|
|
|
259
271
|
Accordionary.init("#my-accordion");
|
|
260
272
|
```
|
|
261
273
|
|
|
274
|
+
## Generating Accordions from JSON
|
|
275
|
+
|
|
276
|
+
When installed via package manager, you can generate accordion HTML from structured JSON data using the `generateAccordionary` function.
|
|
277
|
+
|
|
278
|
+
### Basic Usage
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { generateAccordionary } from "accordionary";
|
|
282
|
+
|
|
283
|
+
const data = {
|
|
284
|
+
items: [
|
|
285
|
+
{
|
|
286
|
+
heading: "Question 1",
|
|
287
|
+
content: "Answer 1",
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
heading: "Question 2",
|
|
291
|
+
content: "Answer 2",
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Generate the accordion element
|
|
297
|
+
const element = generateAccordionary(data);
|
|
298
|
+
|
|
299
|
+
// Append to DOM
|
|
300
|
+
document.body.appendChild(element);
|
|
301
|
+
|
|
302
|
+
// Initialize it
|
|
303
|
+
const accordion = Accordionary.init(element);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Configuration Options
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
const element = generateAccordionary(data, {
|
|
310
|
+
icon: "▼", // HTML string for icon (default: "▼")
|
|
311
|
+
openDefault: "none", // "all" | "first" | "none" (default: "none")
|
|
312
|
+
allowMultiple: true, // Allow multiple items open (default: true)
|
|
313
|
+
speed: 300, // Animation duration in ms (default: 300)
|
|
314
|
+
easing: "ease", // CSS easing function (default: "ease")
|
|
315
|
+
linked: false, // Link all items open/close together (default: false)
|
|
316
|
+
classes: {
|
|
317
|
+
component: ["my-accordion"], // Custom classes for component
|
|
318
|
+
item: ["my-item"], // Custom classes for items
|
|
319
|
+
heading: ["my-heading"], // Custom classes for headings
|
|
320
|
+
content: ["my-content"], // Custom classes for content
|
|
321
|
+
icon: ["my-icon"], // Custom classes for icons
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Custom Icon
|
|
327
|
+
|
|
328
|
+
You can provide any HTML string for the icon - emoji, SVG, or image tag:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// Emoji
|
|
332
|
+
const element = generateAccordionary(data, {
|
|
333
|
+
icon: "👇",
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// SVG
|
|
337
|
+
const element = generateAccordionary(data, {
|
|
338
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
339
|
+
<path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6l-6-6z"/>
|
|
340
|
+
</svg>`,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Image
|
|
344
|
+
const element = generateAccordionary(data, {
|
|
345
|
+
icon: '<img src="/chevron.svg" alt="">',
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Item-Level Configuration
|
|
350
|
+
|
|
351
|
+
Individual items can have their own configuration:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
const data = {
|
|
355
|
+
items: [
|
|
356
|
+
{
|
|
357
|
+
heading: "Normal Item",
|
|
358
|
+
content: "This item follows component defaults",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
heading: "Force Open Item",
|
|
362
|
+
content: "This item starts open regardless of component settings",
|
|
363
|
+
config: {
|
|
364
|
+
openOverride: true,
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
heading: "Disabled Item",
|
|
369
|
+
content: "This item cannot be toggled and is always visible",
|
|
370
|
+
config: {
|
|
371
|
+
disabled: true,
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
};
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### HTML in Content
|
|
379
|
+
|
|
380
|
+
Both `heading` and `content` accept HTML strings:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const data = {
|
|
384
|
+
items: [
|
|
385
|
+
{
|
|
386
|
+
heading: '<div class="font-bold">Rich <em>Heading</em></div>',
|
|
387
|
+
content: `
|
|
388
|
+
<img src="/image.jpg" alt="Description">
|
|
389
|
+
<p>Paragraph with <strong>bold text</strong></p>
|
|
390
|
+
<ul>
|
|
391
|
+
<li>List item 1</li>
|
|
392
|
+
<li>List item 2</li>
|
|
393
|
+
</ul>
|
|
394
|
+
`,
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
};
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### TypeScript Support
|
|
401
|
+
|
|
402
|
+
Full type definitions are included:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import {
|
|
406
|
+
generateAccordionary,
|
|
407
|
+
type AccordionData,
|
|
408
|
+
type GeneratorConfig,
|
|
409
|
+
} from "accordionary";
|
|
410
|
+
|
|
411
|
+
const data: AccordionData = {
|
|
412
|
+
items: [
|
|
413
|
+
{
|
|
414
|
+
heading: "Question",
|
|
415
|
+
content: "Answer",
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const config: GeneratorConfig = {
|
|
421
|
+
openDefault: "first",
|
|
422
|
+
classes: {
|
|
423
|
+
component: ["custom-accordion"],
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const element = generateAccordionary(data, config);
|
|
428
|
+
```
|
|
429
|
+
|
|
262
430
|
## Accessibility
|
|
263
431
|
|
|
264
432
|
Accordionary is built with accessibility in mind:
|
package/dist/accordionary.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var{defineProperty:
|
|
1
|
+
var{defineProperty:k,getOwnPropertyNames:I,getOwnPropertyDescriptor:u}=Object,f=Object.prototype.hasOwnProperty;var w=new WeakMap,A=(j)=>{var q=w.get(j),J;if(q)return q;if(q=k({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")I(j).map((K)=>!f.call(q,K)&&k(q,K,{get:()=>j[K],enumerable:!(J=u(j,K))||J.enumerable}));return w.set(j,q),q};var h=(j,q)=>{for(var J in q)k(j,J,{get:q[J],enumerable:!0,configurable:!0,set:(K)=>q[J]=()=>K})};var r={};h(r,{initAll:()=>O,init:()=>C,generateAccordionary:()=>D,default:()=>p});module.exports=A(r);var E=0;function x(j,q,J,K){let{headingElement:V,contentElement:H,iconElement:P,config:W}=j,Q=`accordionary-${E++}`,U=`${Q}-header`,z=`${Q}-content`,G=!1;if(W.openOverride!==null)G=W.openOverride;else if(q.openDefault==="all")G=!0;else if(q.openDefault==="first"&&J)G=!0;let X=G,{speed:B,easing:L,reduceMotion:Z}=q,T=Z?"none":`height ${B}ms ${L}`,b=Z?"none":`transform ${B}ms ${L}`;if(H.style.overflow="hidden",H.style.transition=T,H.style.height=X?"auto":"0px",!X)H.inert=!0;if(P.style.transition=b,W.disabled)P.style.visibility="hidden";else if(X)P.style.transform="rotate(180deg)";if(V.id=U,V.setAttribute("tabindex",W.disabled?"-1":"0"),V.setAttribute("role","button"),V.setAttribute("aria-expanded",X?"true":"false"),V.setAttribute("aria-controls",z),V.style.cursor=W.disabled?"default":"pointer",W.disabled)V.setAttribute("aria-disabled","true");H.id=z,H.setAttribute("role","region"),H.setAttribute("aria-labelledby",U);function y(){if(X)return;X=!0,H.inert=!1,H.style.height=`${H.scrollHeight}px`,P.style.transform="rotate(180deg)",V.setAttribute("aria-expanded","true")}function v(){if(!X)return;X=!1,H.inert=!0,H.style.height=`${H.scrollHeight}px`,H.offsetHeight,H.style.height="0px",P.style.transform="rotate(0deg)",V.setAttribute("aria-expanded","false")}function M(){if(W.disabled)return;if(q.linked){if(X){for(let Y of K)if(!Y.config.disabled)Y.close?.()}else for(let Y of K)if(!Y.config.disabled)Y.open?.()}else if(X)v();else{if(!q.allowMultiple){for(let Y of K)if(Y!==j&&Y.close)Y.close()}y()}}j.open=y,j.close=v,j.toggle=M,V.addEventListener("click",M),V.addEventListener("keydown",(Y)=>{if(Y.key==="Enter"||Y.key===" ")Y.preventDefault(),M()}),H.addEventListener("transitionend",()=>{if(X)H.style.height="auto"})}function $(j,q){console.warn(`[Accordionary] ${j}`,q||"")}function N(j,q){console.error(`[Accordionary] ${j}`,q||"")}function _(j,q){return j.getAttribute(`accordionary-${q}`)}function R(j,q=document){return q.querySelector(`[accordionary="${j}"]`)}function F(j,q=document){return q.querySelectorAll(`[accordionary="${j}"]`)}var g=window.matchMedia("(prefers-reduced-motion: reduce)").matches;function S(j){if(j.hasAttribute("data-accordionary-initialized"))return null;j.setAttribute("data-accordionary-initialized","true");let q=_(j,"open"),J=_(j,"multiple"),K=_(j,"speed"),V=_(j,"easing"),H=_(j,"link");if(q&&!["all","first","none"].includes(q))$(`Invalid accordionary-open="${q}". Expected "all", "first", or "none". Defaulting to "none".`,j);if(J&&!["true","false"].includes(J))$(`Invalid accordionary-multiple="${J}". Expected "true" or "false". Defaulting to "true".`,j);if(H&&!["true","false"].includes(H))$(`Invalid accordionary-link="${H}". Expected "true" or "false". Defaulting to "false".`,j);let P=K?parseInt(K,10):300;if(K&&(isNaN(P)||P<0))$(`Invalid accordionary-speed="${K}". Expected a positive number in milliseconds. Defaulting to 300.`,j);let W={openDefault:q==="all"||q==="first"?q:"none",allowMultiple:J!=="false",speed:isNaN(P)||P<0?300:P,easing:V||"ease",reduceMotion:g,linked:H==="true"},Q=[],U=F("item",j);if(U.length===0)return $('No items found. Add elements with accordionary="item" inside your component.',j),null;for(let G of U){let X=R("header",G),B=R("content",G),L=R("icon",G);if(!X){N('Missing header element. Add accordionary="header" inside this item.',G);continue}if(!B){N('Missing content element. Add accordionary="content" inside this item.',G);continue}if(!L){N('Missing icon element. Add accordionary="icon" inside your header.',G);continue}let Z=_(G,"open"),T=_(G,"disable");if(Z&&!["true","false"].includes(Z))$(`Invalid accordionary-open="${Z}" on item. Expected "true" or "false".`,G);let b={openOverride:Z==="true"?!0:Z==="false"?!1:null,disabled:T==="true"};Q.push({element:G,headingElement:X,contentElement:B,iconElement:L,config:b})}Q.forEach((G,X)=>{x(G,W,X===0,Q)});let z=Q.map((G)=>({element:G.element,open:()=>G.open?.(),close:()=>G.close?.(),toggle:()=>G.toggle?.()}));return{element:j,openAll:()=>{for(let G of Q)G.open?.()},closeAll:()=>{for(let G of Q)G.close?.()},open:(G)=>{Q[G]?.open?.()},close:(G)=>{Q[G]?.close?.()},toggle:(G)=>{Q[G]?.toggle?.()},items:z}}function D(j,q={}){let{icon:J="▼",openDefault:K="none",allowMultiple:V=!0,speed:H=300,easing:P="ease",linked:W=!1,classes:Q={}}=q,U=document.createElement("div");if(U.setAttribute("accordionary","component"),K!=="none")U.setAttribute("accordionary-open",K);if(!V)U.setAttribute("accordionary-multiple","false");if(H!==300)U.setAttribute("accordionary-speed",H.toString());if(P!=="ease")U.setAttribute("accordionary-easing",P);if(W)U.setAttribute("accordionary-link","true");if(Q.component)U.classList.add(...Q.component);for(let z of j.items){let G=d(z,J,Q);U.appendChild(G)}return U}function d(j,q,J){let{heading:K,content:V,config:H={}}=j,P=document.createElement("div");if(P.setAttribute("accordionary","item"),H.openOverride!==void 0)P.setAttribute("accordionary-open",H.openOverride.toString());if(H.disabled)P.setAttribute("accordionary-disable","true");if(J.item)P.classList.add(...J.item);let W=document.createElement("div");if(W.setAttribute("accordionary","header"),W.innerHTML=K,J.heading)W.classList.add(...J.heading);let Q=document.createElement("span");if(Q.setAttribute("accordionary","icon"),Q.innerHTML=q,J.icon)Q.classList.add(...J.icon);W.appendChild(Q);let U=document.createElement("div");if(U.setAttribute("accordionary","content"),U.innerHTML=V,J.content)U.classList.add(...J.content);return P.appendChild(W),P.appendChild(U),P}function C(j){let q=typeof j==="string"?document.querySelector(j):j;if(!q)return console.error(`[Accordionary] Element not found: ${j}`),null;return S(q)}function O(){let j=F("component"),q=[];for(let J of j){let K=S(J);if(K)q.push(K)}return q}var p={init:C,initAll:O,generateAccordionary:D};
|
package/dist/accordionary.d.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Exports the API for programmatic control of accordions.
|
|
5
5
|
* Use this when installing via NPM for manual initialization.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
8
|
-
|
|
7
|
+
import { generateAccordionary } from "./generate-accordion";
|
|
8
|
+
import type { AccordionController, ItemController, AccordionData, AccordionItemData, AccordionItemConfig, GeneratorConfig, GeneratorClasses } from "./types";
|
|
9
|
+
export type { AccordionController, ItemController, AccordionData, AccordionItemData, AccordionItemConfig, GeneratorConfig, GeneratorClasses, };
|
|
9
10
|
/**
|
|
10
11
|
* Initialize a single accordion component.
|
|
11
12
|
*
|
|
@@ -28,8 +29,10 @@ export declare function init(element: HTMLElement | string): AccordionController
|
|
|
28
29
|
* accordions[0].openAll();
|
|
29
30
|
*/
|
|
30
31
|
export declare function initAll(): AccordionController[];
|
|
32
|
+
export { generateAccordionary };
|
|
31
33
|
declare const _default: {
|
|
32
34
|
init: typeof init;
|
|
33
35
|
initAll: typeof initAll;
|
|
36
|
+
generateAccordionary: typeof generateAccordionary;
|
|
34
37
|
};
|
|
35
38
|
export default _default;
|
package/dist/accordionary.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var w=0;function y(j,
|
|
1
|
+
var w=0;function y(j,G,P,W){let{headingElement:U,contentElement:H,iconElement:J,config:V}=j,K=`accordionary-${w++}`,Q=`${K}-header`,z=`${K}-content`,q=!1;if(V.openOverride!==null)q=V.openOverride;else if(G.openDefault==="all")q=!0;else if(G.openDefault==="first"&&P)q=!0;let X=q,{speed:B,easing:L,reduceMotion:Z}=G,T=Z?"none":`height ${B}ms ${L}`,b=Z?"none":`transform ${B}ms ${L}`;if(H.style.overflow="hidden",H.style.transition=T,H.style.height=X?"auto":"0px",!X)H.inert=!0;if(J.style.transition=b,V.disabled)J.style.visibility="hidden";else if(X)J.style.transform="rotate(180deg)";if(U.id=Q,U.setAttribute("tabindex",V.disabled?"-1":"0"),U.setAttribute("role","button"),U.setAttribute("aria-expanded",X?"true":"false"),U.setAttribute("aria-controls",z),U.style.cursor=V.disabled?"default":"pointer",V.disabled)U.setAttribute("aria-disabled","true");H.id=z,H.setAttribute("role","region"),H.setAttribute("aria-labelledby",Q);function S(){if(X)return;X=!0,H.inert=!1,H.style.height=`${H.scrollHeight}px`,J.style.transform="rotate(180deg)",U.setAttribute("aria-expanded","true")}function D(){if(!X)return;X=!1,H.inert=!0,H.style.height=`${H.scrollHeight}px`,H.offsetHeight,H.style.height="0px",J.style.transform="rotate(0deg)",U.setAttribute("aria-expanded","false")}function M(){if(V.disabled)return;if(G.linked){if(X){for(let Y of W)if(!Y.config.disabled)Y.close?.()}else for(let Y of W)if(!Y.config.disabled)Y.open?.()}else if(X)D();else{if(!G.allowMultiple){for(let Y of W)if(Y!==j&&Y.close)Y.close()}S()}}j.open=S,j.close=D,j.toggle=M,U.addEventListener("click",M),U.addEventListener("keydown",(Y)=>{if(Y.key==="Enter"||Y.key===" ")Y.preventDefault(),M()}),H.addEventListener("transitionend",()=>{if(X)H.style.height="auto"})}function $(j,G){console.warn(`[Accordionary] ${j}`,G||"")}function N(j,G){console.error(`[Accordionary] ${j}`,G||"")}function _(j,G){return j.getAttribute(`accordionary-${G}`)}function R(j,G=document){return G.querySelector(`[accordionary="${j}"]`)}function F(j,G=document){return G.querySelectorAll(`[accordionary="${j}"]`)}var x=window.matchMedia("(prefers-reduced-motion: reduce)").matches;function k(j){if(j.hasAttribute("data-accordionary-initialized"))return null;j.setAttribute("data-accordionary-initialized","true");let G=_(j,"open"),P=_(j,"multiple"),W=_(j,"speed"),U=_(j,"easing"),H=_(j,"link");if(G&&!["all","first","none"].includes(G))$(`Invalid accordionary-open="${G}". Expected "all", "first", or "none". Defaulting to "none".`,j);if(P&&!["true","false"].includes(P))$(`Invalid accordionary-multiple="${P}". Expected "true" or "false". Defaulting to "true".`,j);if(H&&!["true","false"].includes(H))$(`Invalid accordionary-link="${H}". Expected "true" or "false". Defaulting to "false".`,j);let J=W?parseInt(W,10):300;if(W&&(isNaN(J)||J<0))$(`Invalid accordionary-speed="${W}". Expected a positive number in milliseconds. Defaulting to 300.`,j);let V={openDefault:G==="all"||G==="first"?G:"none",allowMultiple:P!=="false",speed:isNaN(J)||J<0?300:J,easing:U||"ease",reduceMotion:x,linked:H==="true"},K=[],Q=F("item",j);if(Q.length===0)return $('No items found. Add elements with accordionary="item" inside your component.',j),null;for(let q of Q){let X=R("header",q),B=R("content",q),L=R("icon",q);if(!X){N('Missing header element. Add accordionary="header" inside this item.',q);continue}if(!B){N('Missing content element. Add accordionary="content" inside this item.',q);continue}if(!L){N('Missing icon element. Add accordionary="icon" inside your header.',q);continue}let Z=_(q,"open"),T=_(q,"disable");if(Z&&!["true","false"].includes(Z))$(`Invalid accordionary-open="${Z}" on item. Expected "true" or "false".`,q);let b={openOverride:Z==="true"?!0:Z==="false"?!1:null,disabled:T==="true"};K.push({element:q,headingElement:X,contentElement:B,iconElement:L,config:b})}K.forEach((q,X)=>{y(q,V,X===0,K)});let z=K.map((q)=>({element:q.element,open:()=>q.open?.(),close:()=>q.close?.(),toggle:()=>q.toggle?.()}));return{element:j,openAll:()=>{for(let q of K)q.open?.()},closeAll:()=>{for(let q of K)q.close?.()},open:(q)=>{K[q]?.open?.()},close:(q)=>{K[q]?.close?.()},toggle:(q)=>{K[q]?.toggle?.()},items:z}}function v(j,G={}){let{icon:P="▼",openDefault:W="none",allowMultiple:U=!0,speed:H=300,easing:J="ease",linked:V=!1,classes:K={}}=G,Q=document.createElement("div");if(Q.setAttribute("accordionary","component"),W!=="none")Q.setAttribute("accordionary-open",W);if(!U)Q.setAttribute("accordionary-multiple","false");if(H!==300)Q.setAttribute("accordionary-speed",H.toString());if(J!=="ease")Q.setAttribute("accordionary-easing",J);if(V)Q.setAttribute("accordionary-link","true");if(K.component)Q.classList.add(...K.component);for(let z of j.items){let q=C(z,P,K);Q.appendChild(q)}return Q}function C(j,G,P){let{heading:W,content:U,config:H={}}=j,J=document.createElement("div");if(J.setAttribute("accordionary","item"),H.openOverride!==void 0)J.setAttribute("accordionary-open",H.openOverride.toString());if(H.disabled)J.setAttribute("accordionary-disable","true");if(P.item)J.classList.add(...P.item);let V=document.createElement("div");if(V.setAttribute("accordionary","header"),V.innerHTML=W,P.heading)V.classList.add(...P.heading);let K=document.createElement("span");if(K.setAttribute("accordionary","icon"),K.innerHTML=G,P.icon)K.classList.add(...P.icon);V.appendChild(K);let Q=document.createElement("div");if(Q.setAttribute("accordionary","content"),Q.innerHTML=U,P.content)Q.classList.add(...P.content);return J.appendChild(V),J.appendChild(Q),J}function O(j){let G=typeof j==="string"?document.querySelector(j):j;if(!G)return console.error(`[Accordionary] Element not found: ${j}`),null;return k(G)}function I(){let j=F("component"),G=[];for(let P of j){let W=k(P);if(W)G.push(W)}return G}var a={init:O,initAll:I,generateAccordionary:v};export{I as initAll,O as init,v as generateAccordionary,a as default};
|
package/dist/accordionary.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{var y=0;function
|
|
1
|
+
(()=>{var y=0;function S(j,G,Z,W){let{headingElement:K,contentElement:H,iconElement:U,config:V}=j,Q=`accordionary-${y++}`,$=`${Q}-header`,N=`${Q}-content`,q=!1;if(V.openOverride!==null)q=V.openOverride;else if(G.openDefault==="all")q=!0;else if(G.openDefault==="first"&&Z)q=!0;let J=q,{speed:B,easing:L,reduceMotion:X}=G,b=X?"none":`height ${B}ms ${L}`,F=X?"none":`transform ${B}ms ${L}`;if(H.style.overflow="hidden",H.style.transition=b,H.style.height=J?"auto":"0px",!J)H.inert=!0;if(U.style.transition=F,V.disabled)U.style.visibility="hidden";else if(J)U.style.transform="rotate(180deg)";if(K.id=$,K.setAttribute("tabindex",V.disabled?"-1":"0"),K.setAttribute("role","button"),K.setAttribute("aria-expanded",J?"true":"false"),K.setAttribute("aria-controls",N),K.style.cursor=V.disabled?"default":"pointer",V.disabled)K.setAttribute("aria-disabled","true");H.id=N,H.setAttribute("role","region"),H.setAttribute("aria-labelledby",$);function k(){if(J)return;J=!0,H.inert=!1,H.style.height=`${H.scrollHeight}px`,U.style.transform="rotate(180deg)",K.setAttribute("aria-expanded","true")}function M(){if(!J)return;J=!1,H.inert=!0,H.style.height=`${H.scrollHeight}px`,H.offsetHeight,H.style.height="0px",U.style.transform="rotate(0deg)",K.setAttribute("aria-expanded","false")}function T(){if(V.disabled)return;if(G.linked){if(J){for(let P of W)if(!P.config.disabled)P.close?.()}else for(let P of W)if(!P.config.disabled)P.open?.()}else if(J)M();else{if(!G.allowMultiple){for(let P of W)if(P!==j&&P.close)P.close()}k()}}j.open=k,j.close=M,j.toggle=T,K.addEventListener("click",T),K.addEventListener("keydown",(P)=>{if(P.key==="Enter"||P.key===" ")P.preventDefault(),T()}),H.addEventListener("transitionend",()=>{if(J)H.style.height="auto"})}function _(j,G){console.warn(`[Accordionary] ${j}`,G||"")}function z(j,G){console.error(`[Accordionary] ${j}`,G||"")}function Y(j,G){return j.getAttribute(`accordionary-${G}`)}function D(j,G=document){return G.querySelector(`[accordionary="${j}"]`)}function R(j,G=document){return G.querySelectorAll(`[accordionary="${j}"]`)}var x=window.matchMedia("(prefers-reduced-motion: reduce)").matches;function v(j){if(j.hasAttribute("data-accordionary-initialized"))return null;j.setAttribute("data-accordionary-initialized","true");let G=Y(j,"open"),Z=Y(j,"multiple"),W=Y(j,"speed"),K=Y(j,"easing"),H=Y(j,"link");if(G&&!["all","first","none"].includes(G))_(`Invalid accordionary-open="${G}". Expected "all", "first", or "none". Defaulting to "none".`,j);if(Z&&!["true","false"].includes(Z))_(`Invalid accordionary-multiple="${Z}". Expected "true" or "false". Defaulting to "true".`,j);if(H&&!["true","false"].includes(H))_(`Invalid accordionary-link="${H}". Expected "true" or "false". Defaulting to "false".`,j);let U=W?parseInt(W,10):300;if(W&&(isNaN(U)||U<0))_(`Invalid accordionary-speed="${W}". Expected a positive number in milliseconds. Defaulting to 300.`,j);let V={openDefault:G==="all"||G==="first"?G:"none",allowMultiple:Z!=="false",speed:isNaN(U)||U<0?300:U,easing:K||"ease",reduceMotion:x,linked:H==="true"},Q=[],$=R("item",j);if($.length===0)return _('No items found. Add elements with accordionary="item" inside your component.',j),null;for(let q of $){let J=D("header",q),B=D("content",q),L=D("icon",q);if(!J){z('Missing header element. Add accordionary="header" inside this item.',q);continue}if(!B){z('Missing content element. Add accordionary="content" inside this item.',q);continue}if(!L){z('Missing icon element. Add accordionary="icon" inside your header.',q);continue}let X=Y(q,"open"),b=Y(q,"disable");if(X&&!["true","false"].includes(X))_(`Invalid accordionary-open="${X}" on item. Expected "true" or "false".`,q);let F={openOverride:X==="true"?!0:X==="false"?!1:null,disabled:b==="true"};Q.push({element:q,headingElement:J,contentElement:B,iconElement:L,config:F})}Q.forEach((q,J)=>{S(q,V,J===0,Q)});let N=Q.map((q)=>({element:q.element,open:()=>q.open?.(),close:()=>q.close?.(),toggle:()=>q.toggle?.()}));return{element:j,openAll:()=>{for(let q of Q)q.open?.()},closeAll:()=>{for(let q of Q)q.close?.()},open:(q)=>{Q[q]?.open?.()},close:(q)=>{Q[q]?.close?.()},toggle:(q)=>{Q[q]?.toggle?.()},items:N}}function w(){let j=R("component");for(let G of j)v(G)}if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",w);else w();})();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accordionary - Accordion Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates accordion HTML elements from structured JSON data.
|
|
5
|
+
* For use with package managers only (not browser implementation).
|
|
6
|
+
*/
|
|
7
|
+
import type { AccordionData, GeneratorConfig } from "./types";
|
|
8
|
+
/**
|
|
9
|
+
* Generates an accordion element from JSON data.
|
|
10
|
+
*
|
|
11
|
+
* @param data - The accordion data with items array
|
|
12
|
+
* @param config - Optional configuration for the accordion
|
|
13
|
+
* @returns An HTMLElement ready to be appended to the DOM and initialized
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const data = {
|
|
18
|
+
* items: [
|
|
19
|
+
* {
|
|
20
|
+
* heading: "Question 1",
|
|
21
|
+
* content: "Answer 1",
|
|
22
|
+
* },
|
|
23
|
+
* {
|
|
24
|
+
* heading: "Question 2",
|
|
25
|
+
* content: "Answer 2",
|
|
26
|
+
* config: { openOverride: true }
|
|
27
|
+
* }
|
|
28
|
+
* ]
|
|
29
|
+
* };
|
|
30
|
+
*
|
|
31
|
+
* const element = generateAccordionary(data, {
|
|
32
|
+
* icon: "▼",
|
|
33
|
+
* openDefault: "none",
|
|
34
|
+
* allowMultiple: true,
|
|
35
|
+
* speed: 300,
|
|
36
|
+
* easing: "ease",
|
|
37
|
+
* classes: {
|
|
38
|
+
* component: ["my-accordion"],
|
|
39
|
+
* item: ["my-item"],
|
|
40
|
+
* }
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* document.body.appendChild(element);
|
|
44
|
+
* const accordion = Accordionary.init(element);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateAccordionary(data: AccordionData, config?: GeneratorConfig): HTMLElement;
|
package/dist/types.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface AccordionConfig {
|
|
|
15
15
|
easing: string;
|
|
16
16
|
/** Whether to disable animations (respects prefers-reduced-motion) */
|
|
17
17
|
reduceMotion: boolean;
|
|
18
|
+
/** Whether all items are linked (open/close together) */
|
|
19
|
+
linked: boolean;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Item-level configuration parsed from HTML attributes.
|
|
@@ -78,3 +80,64 @@ export interface AccordionController {
|
|
|
78
80
|
/** Get all item controllers */
|
|
79
81
|
items: ItemController[];
|
|
80
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Configuration for a single accordion item in generated data.
|
|
85
|
+
*/
|
|
86
|
+
export interface AccordionItemConfig {
|
|
87
|
+
/** Override for initial open state */
|
|
88
|
+
openOverride?: boolean;
|
|
89
|
+
/** Whether the item is non-interactive and always open */
|
|
90
|
+
disabled?: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Data for a single accordion item.
|
|
94
|
+
*/
|
|
95
|
+
export interface AccordionItemData {
|
|
96
|
+
/** HTML content for the heading */
|
|
97
|
+
heading: string;
|
|
98
|
+
/** HTML content for the collapsible content */
|
|
99
|
+
content: string;
|
|
100
|
+
/** Optional item-specific configuration */
|
|
101
|
+
config?: AccordionItemConfig;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Data structure for accordion generation.
|
|
105
|
+
*/
|
|
106
|
+
export interface AccordionData {
|
|
107
|
+
/** Array of accordion items */
|
|
108
|
+
items: AccordionItemData[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* CSS class names to apply to generated accordion elements.
|
|
112
|
+
*/
|
|
113
|
+
export interface GeneratorClasses {
|
|
114
|
+
/** Classes for the accordion component container */
|
|
115
|
+
component?: string[];
|
|
116
|
+
/** Classes for each accordion item */
|
|
117
|
+
item?: string[];
|
|
118
|
+
/** Classes for each accordion heading */
|
|
119
|
+
heading?: string[];
|
|
120
|
+
/** Classes for each accordion content panel */
|
|
121
|
+
content?: string[];
|
|
122
|
+
/** Classes for each accordion icon */
|
|
123
|
+
icon?: string[];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Configuration options for accordion generation.
|
|
127
|
+
*/
|
|
128
|
+
export interface GeneratorConfig {
|
|
129
|
+
/** HTML string for the expand/collapse icon (default: "▼") */
|
|
130
|
+
icon?: string;
|
|
131
|
+
/** Which items to open by default: "all", "first", or "none" (default: "none") */
|
|
132
|
+
openDefault?: "all" | "first" | "none";
|
|
133
|
+
/** Whether multiple items can be open simultaneously (default: false) */
|
|
134
|
+
allowMultiple?: boolean;
|
|
135
|
+
/** Animation duration in milliseconds (default: 300) */
|
|
136
|
+
speed?: number;
|
|
137
|
+
/** CSS easing function for animations (default: "ease") */
|
|
138
|
+
easing?: string;
|
|
139
|
+
/** Whether all items are linked (open/close together) (default: false) */
|
|
140
|
+
linked?: boolean;
|
|
141
|
+
/** Optional CSS class names to apply to elements */
|
|
142
|
+
classes?: GeneratorClasses;
|
|
143
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accordionary",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A lightweight, accessible, vanilla JavaScript accordion with zero dependencies",
|
|
5
5
|
"author": "Brian Tucker",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"url": "https://github.com/briantucker/accordionary/issues"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@types/bun": "latest"
|
|
56
|
+
"@types/bun": "latest",
|
|
57
|
+
"happy-dom": "^20.4.0"
|
|
57
58
|
}
|
|
58
59
|
}
|