@refrakt-md/behaviors 0.5.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/dist/behaviors/accordion.d.ts +11 -0
- package/dist/behaviors/accordion.d.ts.map +1 -0
- package/dist/behaviors/accordion.js +85 -0
- package/dist/behaviors/accordion.js.map +1 -0
- package/dist/behaviors/copy.d.ts +9 -0
- package/dist/behaviors/copy.d.ts.map +1 -0
- package/dist/behaviors/copy.js +53 -0
- package/dist/behaviors/copy.js.map +1 -0
- package/dist/behaviors/datatable.d.ts +17 -0
- package/dist/behaviors/datatable.d.ts.map +1 -0
- package/dist/behaviors/datatable.js +210 -0
- package/dist/behaviors/datatable.js.map +1 -0
- package/dist/behaviors/form.d.ts +18 -0
- package/dist/behaviors/form.d.ts.map +1 -0
- package/dist/behaviors/form.js +88 -0
- package/dist/behaviors/form.js.map +1 -0
- package/dist/behaviors/preview.d.ts +20 -0
- package/dist/behaviors/preview.d.ts.map +1 -0
- package/dist/behaviors/preview.js +302 -0
- package/dist/behaviors/preview.js.map +1 -0
- package/dist/behaviors/reveal.d.ts +9 -0
- package/dist/behaviors/reveal.d.ts.map +1 -0
- package/dist/behaviors/reveal.js +70 -0
- package/dist/behaviors/reveal.js.map +1 -0
- package/dist/behaviors/tabs.d.ts +13 -0
- package/dist/behaviors/tabs.d.ts.map +1 -0
- package/dist/behaviors/tabs.js +142 -0
- package/dist/behaviors/tabs.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +18 -0
- package/dist/utils.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CleanupFn } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Accordion behavior for `[data-rune="accordion"]`.
|
|
4
|
+
*
|
|
5
|
+
* Enhances native `<details>/<summary>` elements with:
|
|
6
|
+
* - Exclusive mode: only one item open at a time (when `data-multiple` is absent)
|
|
7
|
+
* - ARIA attributes: `aria-expanded` on triggers, `aria-controls`/`aria-labelledby` wiring
|
|
8
|
+
* - Keyboard navigation: ArrowUp/Down between triggers, Home/End to first/last
|
|
9
|
+
*/
|
|
10
|
+
export declare function accordionBehavior(el: HTMLElement): CleanupFn;
|
|
11
|
+
//# sourceMappingURL=accordion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accordion.d.ts","sourceRoot":"","sources":["../../src/behaviors/accordion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,WAAW,GAAG,SAAS,CAwF5D"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { uniqueId } from '../utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Accordion behavior for `[data-rune="accordion"]`.
|
|
4
|
+
*
|
|
5
|
+
* Enhances native `<details>/<summary>` elements with:
|
|
6
|
+
* - Exclusive mode: only one item open at a time (when `data-multiple` is absent)
|
|
7
|
+
* - ARIA attributes: `aria-expanded` on triggers, `aria-controls`/`aria-labelledby` wiring
|
|
8
|
+
* - Keyboard navigation: ArrowUp/Down between triggers, Home/End to first/last
|
|
9
|
+
*/
|
|
10
|
+
export function accordionBehavior(el) {
|
|
11
|
+
const details = Array.from(el.querySelectorAll('details'));
|
|
12
|
+
if (details.length === 0)
|
|
13
|
+
return () => { };
|
|
14
|
+
const allowMultiple = el.hasAttribute('data-multiple');
|
|
15
|
+
const cleanups = [];
|
|
16
|
+
// Wire up ARIA and event listeners for each item
|
|
17
|
+
for (const item of details) {
|
|
18
|
+
const summary = item.querySelector('summary');
|
|
19
|
+
if (!summary)
|
|
20
|
+
continue;
|
|
21
|
+
// Generate IDs for ARIA wiring
|
|
22
|
+
const panelId = uniqueId('rf-accordion-panel');
|
|
23
|
+
const triggerId = uniqueId('rf-accordion-trigger');
|
|
24
|
+
summary.id = triggerId;
|
|
25
|
+
summary.setAttribute('aria-expanded', String(item.open));
|
|
26
|
+
// Find the content panel (first non-summary child element)
|
|
27
|
+
const panel = Array.from(item.children).find((child) => child instanceof HTMLElement && child.tagName !== 'SUMMARY');
|
|
28
|
+
if (panel) {
|
|
29
|
+
panel.id = panelId;
|
|
30
|
+
panel.setAttribute('role', 'region');
|
|
31
|
+
panel.setAttribute('aria-labelledby', triggerId);
|
|
32
|
+
summary.setAttribute('aria-controls', panelId);
|
|
33
|
+
}
|
|
34
|
+
// Toggle handler for exclusive mode
|
|
35
|
+
const onToggle = () => {
|
|
36
|
+
summary.setAttribute('aria-expanded', String(item.open));
|
|
37
|
+
if (!allowMultiple && item.open) {
|
|
38
|
+
for (const other of details) {
|
|
39
|
+
if (other !== item && other.open) {
|
|
40
|
+
other.open = false;
|
|
41
|
+
const otherSummary = other.querySelector('summary');
|
|
42
|
+
otherSummary?.setAttribute('aria-expanded', 'false');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
item.addEventListener('toggle', onToggle);
|
|
48
|
+
cleanups.push(() => item.removeEventListener('toggle', onToggle));
|
|
49
|
+
}
|
|
50
|
+
// Keyboard navigation across all summaries
|
|
51
|
+
const summaries = details
|
|
52
|
+
.map((d) => d.querySelector('summary'))
|
|
53
|
+
.filter((s) => s !== null);
|
|
54
|
+
const onKeydown = (e) => {
|
|
55
|
+
const target = e.target;
|
|
56
|
+
const index = summaries.indexOf(target);
|
|
57
|
+
if (index === -1)
|
|
58
|
+
return;
|
|
59
|
+
let next = null;
|
|
60
|
+
switch (e.key) {
|
|
61
|
+
case 'ArrowDown':
|
|
62
|
+
next = (index + 1) % summaries.length;
|
|
63
|
+
break;
|
|
64
|
+
case 'ArrowUp':
|
|
65
|
+
next = (index - 1 + summaries.length) % summaries.length;
|
|
66
|
+
break;
|
|
67
|
+
case 'Home':
|
|
68
|
+
next = 0;
|
|
69
|
+
break;
|
|
70
|
+
case 'End':
|
|
71
|
+
next = summaries.length - 1;
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (next !== null) {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
summaries[next].focus();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
el.addEventListener('keydown', onKeydown);
|
|
82
|
+
cleanups.push(() => el.removeEventListener('keydown', onKeydown));
|
|
83
|
+
return () => cleanups.forEach((fn) => fn());
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=accordion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accordion.js","sourceRoot":"","sources":["../../src/behaviors/accordion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CleanupFn } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Copy-to-clipboard behavior for code blocks.
|
|
4
|
+
*
|
|
5
|
+
* Finds all `<pre>` elements within the container and injects a copy button.
|
|
6
|
+
* Works on standalone code blocks and code blocks inside rune containers.
|
|
7
|
+
*/
|
|
8
|
+
export declare function copyBehavior(container: HTMLElement | Document): CleanupFn;
|
|
9
|
+
//# sourceMappingURL=copy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../../src/behaviors/copy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAiDzE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy-to-clipboard behavior for code blocks.
|
|
3
|
+
*
|
|
4
|
+
* Finds all `<pre>` elements within the container and injects a copy button.
|
|
5
|
+
* Works on standalone code blocks and code blocks inside rune containers.
|
|
6
|
+
*/
|
|
7
|
+
export function copyBehavior(container) {
|
|
8
|
+
const pres = container.querySelectorAll('pre');
|
|
9
|
+
const cleanups = [];
|
|
10
|
+
for (const pre of pres) {
|
|
11
|
+
// Skip if already wrapped with a copy button (by this behavior or by a framework component)
|
|
12
|
+
if (pre.parentElement?.classList.contains('rf-code-wrapper'))
|
|
13
|
+
continue;
|
|
14
|
+
if (pre.parentElement?.classList.contains('rf-codeblock'))
|
|
15
|
+
continue;
|
|
16
|
+
const btn = document.createElement('button');
|
|
17
|
+
btn.className = 'rf-copy-btn';
|
|
18
|
+
btn.type = 'button';
|
|
19
|
+
btn.setAttribute('aria-label', 'Copy code');
|
|
20
|
+
btn.textContent = 'Copy';
|
|
21
|
+
let timeout;
|
|
22
|
+
const handler = () => {
|
|
23
|
+
const text = pre.textContent ?? '';
|
|
24
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
25
|
+
btn.textContent = 'Copied!';
|
|
26
|
+
btn.setAttribute('aria-label', 'Copied');
|
|
27
|
+
if (timeout)
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
timeout = setTimeout(() => {
|
|
30
|
+
btn.textContent = 'Copy';
|
|
31
|
+
btn.setAttribute('aria-label', 'Copy code');
|
|
32
|
+
}, 2000);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
btn.addEventListener('click', handler);
|
|
36
|
+
// Wrap pre in a relative container for positioning if not already wrapped
|
|
37
|
+
const wrapper = document.createElement('div');
|
|
38
|
+
wrapper.className = 'rf-code-wrapper';
|
|
39
|
+
pre.parentNode?.insertBefore(wrapper, pre);
|
|
40
|
+
wrapper.appendChild(pre);
|
|
41
|
+
wrapper.appendChild(btn);
|
|
42
|
+
cleanups.push(() => {
|
|
43
|
+
btn.removeEventListener('click', handler);
|
|
44
|
+
if (timeout)
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
// Unwrap: move pre back out, remove wrapper and button
|
|
47
|
+
wrapper.parentNode?.insertBefore(pre, wrapper);
|
|
48
|
+
wrapper.remove();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return () => cleanups.forEach((fn) => fn());
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy.js","sourceRoot":"","sources":["../../src/behaviors/copy.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiC;IAC7D,MAAM,IAAI,GAAG,SAAS,CAAC,gBAAgB,CAAc,KAAK,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAsB,EAAE,CAAC;IAEvC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,4FAA4F;QAC5F,IAAI,GAAG,CAAC,aAAa,EAAE,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YAAE,SAAS;QACvE,IAAI,GAAG,CAAC,aAAa,EAAE,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC;YAAE,SAAS;QAEpE,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC7C,GAAG,CAAC,SAAS,GAAG,aAAa,CAAC;QAC9B,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC;QACpB,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QAC5C,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC;QAEzB,IAAI,OAAkD,CAAC;QAEvD,MAAM,OAAO,GAAG,GAAG,EAAE;YACpB,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;YACnC,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC7C,GAAG,CAAC,WAAW,GAAG,SAAS,CAAC;gBAC5B,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;gBACzC,IAAI,OAAO;oBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;gBACnC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;oBACzB,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC;oBACzB,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;gBAC7C,CAAC,EAAE,IAAI,CAAC,CAAC;YACV,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC;QAEF,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAEvC,0EAA0E;QAC1E,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9C,OAAO,CAAC,SAAS,GAAG,iBAAiB,CAAC;QACtC,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC3C,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAEzB,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE;YAClB,GAAG,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC1C,IAAI,OAAO;gBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;YACnC,uDAAuD;YACvD,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC/C,OAAO,CAAC,MAAM,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CleanupFn } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* DataTable behavior for `[data-rune="datatable"]`.
|
|
4
|
+
*
|
|
5
|
+
* Enhances a rendered `<table>` element with:
|
|
6
|
+
* - Search/filter: input that filters rows by cell text
|
|
7
|
+
* - Column sorting: clickable headers with sort indicators
|
|
8
|
+
* - Pagination: page navigation with prev/next buttons
|
|
9
|
+
*
|
|
10
|
+
* Reads configuration from data attributes set by the identity transform:
|
|
11
|
+
* - `data-searchable`: "true" to enable search
|
|
12
|
+
* - `data-sortable`: comma-separated column names
|
|
13
|
+
* - `data-pagesize`: number of rows per page (0 = no pagination)
|
|
14
|
+
* - `data-defaultsort`: column name to sort by initially
|
|
15
|
+
*/
|
|
16
|
+
export declare function datatableBehavior(el: HTMLElement): CleanupFn;
|
|
17
|
+
//# sourceMappingURL=datatable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datatable.d.ts","sourceRoot":"","sources":["../../src/behaviors/datatable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAO7C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,WAAW,GAAG,SAAS,CAqO5D"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTable behavior for `[data-rune="datatable"]`.
|
|
3
|
+
*
|
|
4
|
+
* Enhances a rendered `<table>` element with:
|
|
5
|
+
* - Search/filter: input that filters rows by cell text
|
|
6
|
+
* - Column sorting: clickable headers with sort indicators
|
|
7
|
+
* - Pagination: page navigation with prev/next buttons
|
|
8
|
+
*
|
|
9
|
+
* Reads configuration from data attributes set by the identity transform:
|
|
10
|
+
* - `data-searchable`: "true" to enable search
|
|
11
|
+
* - `data-sortable`: comma-separated column names
|
|
12
|
+
* - `data-pagesize`: number of rows per page (0 = no pagination)
|
|
13
|
+
* - `data-defaultsort`: column name to sort by initially
|
|
14
|
+
*/
|
|
15
|
+
export function datatableBehavior(el) {
|
|
16
|
+
const table = el.querySelector('table');
|
|
17
|
+
if (!table)
|
|
18
|
+
return () => { };
|
|
19
|
+
// Read configuration from data attributes
|
|
20
|
+
const searchable = el.getAttribute('data-searchable') === 'true';
|
|
21
|
+
const sortableStr = el.getAttribute('data-sortable') || '';
|
|
22
|
+
const sortable = sortableStr.split(',').map((s) => s.trim()).filter(Boolean);
|
|
23
|
+
const pageSize = parseInt(el.getAttribute('data-pagesize') || '0', 10);
|
|
24
|
+
const defaultSort = el.getAttribute('data-defaultsort') || '';
|
|
25
|
+
// Parse table structure
|
|
26
|
+
const thEls = Array.from(table.querySelectorAll('th'));
|
|
27
|
+
const headers = thEls.map((th) => th.textContent?.trim() || '');
|
|
28
|
+
const bodyRows = table.querySelectorAll('tbody tr');
|
|
29
|
+
const rows = Array.from(bodyRows.length > 0 ? bodyRows : table.querySelectorAll('tr:not(:first-child)')).map((tr) => ({
|
|
30
|
+
el: tr,
|
|
31
|
+
cells: Array.from(tr.querySelectorAll('td')).map((td) => td.textContent?.trim() || ''),
|
|
32
|
+
}));
|
|
33
|
+
// Save original row order for cleanup
|
|
34
|
+
const originalOrder = rows.map((r) => r.el);
|
|
35
|
+
// State
|
|
36
|
+
let searchQuery = '';
|
|
37
|
+
let sortColumn = defaultSort;
|
|
38
|
+
let sortDirection = 'asc';
|
|
39
|
+
let currentPage = 0;
|
|
40
|
+
const cleanups = [];
|
|
41
|
+
// Inject search toolbar
|
|
42
|
+
let toolbar = null;
|
|
43
|
+
let searchInput = null;
|
|
44
|
+
if (searchable) {
|
|
45
|
+
toolbar = document.createElement('div');
|
|
46
|
+
toolbar.className = 'rf-datatable__toolbar';
|
|
47
|
+
searchInput = document.createElement('input');
|
|
48
|
+
searchInput.type = 'search';
|
|
49
|
+
searchInput.placeholder = 'Filter rows...';
|
|
50
|
+
searchInput.className = 'rf-datatable__input';
|
|
51
|
+
toolbar.appendChild(searchInput);
|
|
52
|
+
table.before(toolbar);
|
|
53
|
+
const onInput = () => {
|
|
54
|
+
searchQuery = searchInput.value;
|
|
55
|
+
currentPage = 0;
|
|
56
|
+
render();
|
|
57
|
+
};
|
|
58
|
+
searchInput.addEventListener('input', onInput);
|
|
59
|
+
cleanups.push(() => searchInput.removeEventListener('input', onInput));
|
|
60
|
+
}
|
|
61
|
+
// Make sortable headers clickable
|
|
62
|
+
for (const th of thEls) {
|
|
63
|
+
const name = th.textContent?.trim() || '';
|
|
64
|
+
if (!sortable.includes(name))
|
|
65
|
+
continue;
|
|
66
|
+
th.style.cursor = 'pointer';
|
|
67
|
+
th.style.userSelect = 'none';
|
|
68
|
+
const onClick = () => {
|
|
69
|
+
if (sortColumn === name) {
|
|
70
|
+
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
sortColumn = name;
|
|
74
|
+
sortDirection = 'asc';
|
|
75
|
+
}
|
|
76
|
+
currentPage = 0;
|
|
77
|
+
render();
|
|
78
|
+
};
|
|
79
|
+
th.addEventListener('click', onClick);
|
|
80
|
+
cleanups.push(() => {
|
|
81
|
+
th.removeEventListener('click', onClick);
|
|
82
|
+
th.style.cursor = '';
|
|
83
|
+
th.style.userSelect = '';
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Inject pagination controls
|
|
87
|
+
let pagination = null;
|
|
88
|
+
let prevBtn = null;
|
|
89
|
+
let nextBtn = null;
|
|
90
|
+
let pageInfo = null;
|
|
91
|
+
if (pageSize > 0) {
|
|
92
|
+
pagination = document.createElement('div');
|
|
93
|
+
pagination.className = 'rf-datatable__pagination';
|
|
94
|
+
prevBtn = document.createElement('button');
|
|
95
|
+
prevBtn.className = 'rf-datatable__page-btn';
|
|
96
|
+
prevBtn.innerHTML = '← Prev';
|
|
97
|
+
pageInfo = document.createElement('span');
|
|
98
|
+
pageInfo.className = 'rf-datatable__page-info';
|
|
99
|
+
nextBtn = document.createElement('button');
|
|
100
|
+
nextBtn.className = 'rf-datatable__page-btn';
|
|
101
|
+
nextBtn.innerHTML = 'Next →';
|
|
102
|
+
pagination.appendChild(prevBtn);
|
|
103
|
+
pagination.appendChild(pageInfo);
|
|
104
|
+
pagination.appendChild(nextBtn);
|
|
105
|
+
table.after(pagination);
|
|
106
|
+
const onPrev = () => {
|
|
107
|
+
if (currentPage > 0) {
|
|
108
|
+
currentPage--;
|
|
109
|
+
render();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const onNext = () => {
|
|
113
|
+
currentPage++;
|
|
114
|
+
render();
|
|
115
|
+
};
|
|
116
|
+
prevBtn.addEventListener('click', onPrev);
|
|
117
|
+
nextBtn.addEventListener('click', onNext);
|
|
118
|
+
cleanups.push(() => {
|
|
119
|
+
prevBtn.removeEventListener('click', onPrev);
|
|
120
|
+
nextBtn.removeEventListener('click', onNext);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function render() {
|
|
124
|
+
let filtered = [...rows];
|
|
125
|
+
// Filter
|
|
126
|
+
if (searchQuery) {
|
|
127
|
+
const q = searchQuery.toLowerCase();
|
|
128
|
+
filtered = filtered.filter((r) => r.cells.some((c) => c.toLowerCase().includes(q)));
|
|
129
|
+
}
|
|
130
|
+
// Sort
|
|
131
|
+
if (sortColumn) {
|
|
132
|
+
const idx = headers.indexOf(sortColumn);
|
|
133
|
+
if (idx >= 0) {
|
|
134
|
+
filtered.sort((a, b) => {
|
|
135
|
+
const cmp = a.cells[idx].localeCompare(b.cells[idx], undefined, { numeric: true });
|
|
136
|
+
return sortDirection === 'asc' ? cmp : -cmp;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const totalFiltered = filtered.length;
|
|
141
|
+
const totalPages = pageSize > 0 ? Math.ceil(totalFiltered / pageSize) : 1;
|
|
142
|
+
// Clamp current page
|
|
143
|
+
if (currentPage >= totalPages)
|
|
144
|
+
currentPage = Math.max(0, totalPages - 1);
|
|
145
|
+
// Determine visible rows
|
|
146
|
+
const visible = pageSize > 0
|
|
147
|
+
? filtered.slice(currentPage * pageSize, (currentPage + 1) * pageSize)
|
|
148
|
+
: filtered;
|
|
149
|
+
const tbody = table.querySelector('tbody') || table;
|
|
150
|
+
// Hide all, then show and reorder visible
|
|
151
|
+
for (const r of rows)
|
|
152
|
+
r.el.style.display = 'none';
|
|
153
|
+
for (const r of visible) {
|
|
154
|
+
r.el.style.display = '';
|
|
155
|
+
tbody.appendChild(r.el);
|
|
156
|
+
}
|
|
157
|
+
// Update sort indicators on headers
|
|
158
|
+
for (const th of thEls) {
|
|
159
|
+
const name = th.textContent?.replace(/[▲▼]/g, '').trim() || '';
|
|
160
|
+
const indicator = th.querySelector('.rf-datatable__sort-indicator');
|
|
161
|
+
if (sortable.includes(name)) {
|
|
162
|
+
if (sortColumn === name) {
|
|
163
|
+
if (indicator) {
|
|
164
|
+
indicator.textContent = sortDirection === 'asc' ? ' ▲' : ' ▼';
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const span = document.createElement('span');
|
|
168
|
+
span.className = 'rf-datatable__sort-indicator';
|
|
169
|
+
span.textContent = sortDirection === 'asc' ? ' ▲' : ' ▼';
|
|
170
|
+
th.appendChild(span);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
indicator?.remove();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Update pagination
|
|
179
|
+
if (pagination && prevBtn && nextBtn && pageInfo) {
|
|
180
|
+
prevBtn.disabled = currentPage === 0;
|
|
181
|
+
nextBtn.disabled = currentPage >= totalPages - 1;
|
|
182
|
+
pageInfo.textContent = `${currentPage + 1} / ${totalPages}`;
|
|
183
|
+
if (totalPages <= 1) {
|
|
184
|
+
pagination.hidden = true;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
pagination.hidden = false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Initial render
|
|
192
|
+
render();
|
|
193
|
+
return () => {
|
|
194
|
+
cleanups.forEach((fn) => fn());
|
|
195
|
+
// Remove injected elements
|
|
196
|
+
toolbar?.remove();
|
|
197
|
+
pagination?.remove();
|
|
198
|
+
// Remove sort indicators
|
|
199
|
+
for (const th of thEls) {
|
|
200
|
+
th.querySelector('.rf-datatable__sort-indicator')?.remove();
|
|
201
|
+
}
|
|
202
|
+
// Restore original row order and visibility
|
|
203
|
+
const tbody = table.querySelector('tbody') || table;
|
|
204
|
+
for (const row of originalOrder) {
|
|
205
|
+
row.style.display = '';
|
|
206
|
+
tbody.appendChild(row);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=datatable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datatable.js","sourceRoot":"","sources":["../../src/behaviors/datatable.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CleanupFn } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Form behavior for `[data-rune="form"]`.
|
|
4
|
+
*
|
|
5
|
+
* Enhances a rendered `<form>` element with:
|
|
6
|
+
* - Async submission via fetch (prevents full page reload)
|
|
7
|
+
* - Honeypot field for basic spam protection
|
|
8
|
+
* - Status messages (submitting, success, error) with ARIA roles
|
|
9
|
+
*
|
|
10
|
+
* Reads configuration from data attributes:
|
|
11
|
+
* - `data-action`: form submission URL
|
|
12
|
+
* - `data-method`: HTTP method (default: POST)
|
|
13
|
+
* - `data-success`: success message text
|
|
14
|
+
* - `data-error`: error message text
|
|
15
|
+
* - `data-honeypot`: "false" to disable honeypot (default: enabled)
|
|
16
|
+
*/
|
|
17
|
+
export declare function formBehavior(el: HTMLElement): CleanupFn;
|
|
18
|
+
//# sourceMappingURL=form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/behaviors/form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,WAAW,GAAG,SAAS,CAgFvD"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form behavior for `[data-rune="form"]`.
|
|
3
|
+
*
|
|
4
|
+
* Enhances a rendered `<form>` element with:
|
|
5
|
+
* - Async submission via fetch (prevents full page reload)
|
|
6
|
+
* - Honeypot field for basic spam protection
|
|
7
|
+
* - Status messages (submitting, success, error) with ARIA roles
|
|
8
|
+
*
|
|
9
|
+
* Reads configuration from data attributes:
|
|
10
|
+
* - `data-action`: form submission URL
|
|
11
|
+
* - `data-method`: HTTP method (default: POST)
|
|
12
|
+
* - `data-success`: success message text
|
|
13
|
+
* - `data-error`: error message text
|
|
14
|
+
* - `data-honeypot`: "false" to disable honeypot (default: enabled)
|
|
15
|
+
*/
|
|
16
|
+
export function formBehavior(el) {
|
|
17
|
+
// The element may be a <form> itself or contain one
|
|
18
|
+
const form = el.tagName === 'FORM' ? el : el.querySelector('form');
|
|
19
|
+
if (!form)
|
|
20
|
+
return () => { };
|
|
21
|
+
const action = el.getAttribute('data-action') || form.getAttribute('action') || '';
|
|
22
|
+
const method = el.getAttribute('data-method') || form.getAttribute('method') || 'POST';
|
|
23
|
+
const successMsg = el.getAttribute('data-success') || 'Form submitted successfully.';
|
|
24
|
+
const errorMsg = el.getAttribute('data-error') || 'Something went wrong. Please try again.';
|
|
25
|
+
const honeypotEnabled = el.getAttribute('data-honeypot') !== 'false';
|
|
26
|
+
// Inject honeypot field
|
|
27
|
+
let honeypotDiv = null;
|
|
28
|
+
if (honeypotEnabled) {
|
|
29
|
+
honeypotDiv = document.createElement('div');
|
|
30
|
+
honeypotDiv.className = 'rf-form__hp';
|
|
31
|
+
honeypotDiv.setAttribute('aria-hidden', 'true');
|
|
32
|
+
honeypotDiv.style.display = 'none';
|
|
33
|
+
const honeypotInput = document.createElement('input');
|
|
34
|
+
honeypotInput.type = 'text';
|
|
35
|
+
honeypotInput.name = '_gotcha';
|
|
36
|
+
honeypotInput.setAttribute('autocomplete', 'off');
|
|
37
|
+
honeypotInput.tabIndex = -1;
|
|
38
|
+
honeypotDiv.appendChild(honeypotInput);
|
|
39
|
+
form.prepend(honeypotDiv);
|
|
40
|
+
}
|
|
41
|
+
// Create status element
|
|
42
|
+
const statusEl = document.createElement('div');
|
|
43
|
+
statusEl.className = 'rf-form__status';
|
|
44
|
+
statusEl.hidden = true;
|
|
45
|
+
form.appendChild(statusEl);
|
|
46
|
+
function showStatus(type, message) {
|
|
47
|
+
statusEl.hidden = false;
|
|
48
|
+
statusEl.className = `rf-form__status rf-form__status--${type}`;
|
|
49
|
+
statusEl.setAttribute('role', type === 'submitting' ? 'status' : 'alert');
|
|
50
|
+
statusEl.textContent = message;
|
|
51
|
+
}
|
|
52
|
+
function hideStatus() {
|
|
53
|
+
statusEl.hidden = true;
|
|
54
|
+
statusEl.className = 'rf-form__status';
|
|
55
|
+
statusEl.textContent = '';
|
|
56
|
+
}
|
|
57
|
+
const onSubmit = async (e) => {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
if (!action)
|
|
60
|
+
return;
|
|
61
|
+
showStatus('submitting', 'Submitting...');
|
|
62
|
+
try {
|
|
63
|
+
const formData = new FormData(form);
|
|
64
|
+
const response = await fetch(action, {
|
|
65
|
+
method,
|
|
66
|
+
body: formData,
|
|
67
|
+
headers: { 'Accept': 'application/json' },
|
|
68
|
+
});
|
|
69
|
+
if (response.ok) {
|
|
70
|
+
showStatus('success', successMsg);
|
|
71
|
+
form.reset();
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
showStatus('error', errorMsg);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
showStatus('error', errorMsg);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
form.addEventListener('submit', onSubmit);
|
|
82
|
+
return () => {
|
|
83
|
+
form.removeEventListener('submit', onSubmit);
|
|
84
|
+
honeypotDiv?.remove();
|
|
85
|
+
statusEl.remove();
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/behaviors/form.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CleanupFn } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Preview behavior for `[data-rune="preview"]`.
|
|
4
|
+
*
|
|
5
|
+
* Creates a toolbar with:
|
|
6
|
+
* - View toggle: switch between preview and source code views
|
|
7
|
+
* - Viewport toggle: responsive viewport presets (mobile, tablet, desktop)
|
|
8
|
+
* - Theme toggle: auto, light, dark theme modes
|
|
9
|
+
*
|
|
10
|
+
* Source panel supports up to three tabs:
|
|
11
|
+
* - Markdoc: original authoring syntax (from source property)
|
|
12
|
+
* - Rune: pre-engine structural HTML (from htmlSource property)
|
|
13
|
+
* - HTML: post-engine themed HTML (from themedSource property, generated at build time)
|
|
14
|
+
*
|
|
15
|
+
* Reads configuration from data attributes:
|
|
16
|
+
* - `data-theme`: initial theme mode (default: 'auto')
|
|
17
|
+
* - `data-responsive`: comma-separated viewport presets
|
|
18
|
+
*/
|
|
19
|
+
export declare function previewBehavior(el: HTMLElement): CleanupFn;
|
|
20
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/behaviors/preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAwC7C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,SAAS,CAwS1D"}
|