@fsegurai/marked-extended-tabs 17.0.0-beta.3 → 17.0.0-beta.5
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/LICENSE +21 -0
- package/README.custom.md +4 -4
- package/README.md +5 -4
- package/dist/index.cjs +22 -475
- package/dist/index.esm.js +226 -472
- package/dist/index.umd.js +22 -481
- package/package.json +4 -4
- package/dist/index.cjs.map +0 -1
- package/dist/index.esm.js.map +0 -1
- package/dist/index.umd.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fsegurai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.custom.md
CHANGED
|
@@ -712,7 +712,7 @@ Individual tabs can use these aliases:
|
|
|
712
712
|
The marked-extended-tabs extension accepts the following configuration options:
|
|
713
713
|
|
|
714
714
|
- `className`: The base CSS class name for tabs container. Defaults to 'marked-extended-tabs-container'.
|
|
715
|
-
- `persistSelection`: Whether to remember tab selection across page reloads. Defaults to **
|
|
715
|
+
- `persistSelection`: Whether to remember tab selection across page reloads. Defaults to **false**.
|
|
716
716
|
- `animation`: Animation type for tab transitions. Defaults to 'fade'. See [Animation Types](#animation-types).
|
|
717
717
|
- `autoActivate`: Automatically activate the first tab if none is explicitly active. Defaults to true.
|
|
718
718
|
- `template`: A custom HTML template for the tabs structure. Defaults to the built-in template.
|
|
@@ -782,7 +782,7 @@ Enable/disable with:
|
|
|
782
782
|
|
|
783
783
|
```javascript
|
|
784
784
|
marked.use(markedExtendedTabs({
|
|
785
|
-
persistSelection: true // Default:
|
|
785
|
+
persistSelection: true // Default: false
|
|
786
786
|
}));
|
|
787
787
|
```
|
|
788
788
|
|
|
@@ -859,7 +859,7 @@ import '@fsegurai/marked-extended-tabs/styles/tabs.css';
|
|
|
859
859
|
marked.use(markedExtendedTabs({
|
|
860
860
|
animation: 'fade',
|
|
861
861
|
autoActivate: true,
|
|
862
|
-
persistSelection:
|
|
862
|
+
persistSelection: false,
|
|
863
863
|
enableKeyboardNavigation: true,
|
|
864
864
|
enableFocusManagement: true,
|
|
865
865
|
|
|
@@ -968,7 +968,7 @@ import markedExtendedTabs from "@fsegurai/marked-extended-tabs";
|
|
|
968
968
|
marked.use(markedExtendedTabs({
|
|
969
969
|
className: 'my-custom-tabs',
|
|
970
970
|
animation: 'slide',
|
|
971
|
-
persistSelection:
|
|
971
|
+
persistSelection: false,
|
|
972
972
|
autoActivate: true,
|
|
973
973
|
customizeToken: (token) => {
|
|
974
974
|
// Add custom logic here
|
package/README.md
CHANGED
|
@@ -763,7 +763,7 @@ Individual tabs can use these aliases:
|
|
|
763
763
|
The marked-extended-tabs extension accepts the following configuration options:
|
|
764
764
|
|
|
765
765
|
- `className`: The base CSS class name for tabs container. Defaults to 'marked-extended-tabs-container'.
|
|
766
|
-
- `persistSelection`: Whether to remember tab selection across page reloads. Defaults to **
|
|
766
|
+
- `persistSelection`: Whether to remember tab selection across page reloads. Defaults to **false**.
|
|
767
767
|
- `animation`: Animation type for tab transitions. Defaults to 'fade'. See [Animation Types](#animation-types).
|
|
768
768
|
- `autoActivate`: Automatically activate the first tab if none is explicitly active. Defaults to true.
|
|
769
769
|
- `template`: A custom HTML template for the tabs structure. Defaults to the built-in template.
|
|
@@ -833,7 +833,7 @@ Enable/disable with:
|
|
|
833
833
|
|
|
834
834
|
```javascript
|
|
835
835
|
marked.use(markedExtendedTabs({
|
|
836
|
-
persistSelection: true // Default:
|
|
836
|
+
persistSelection: true // Default: false
|
|
837
837
|
}));
|
|
838
838
|
```
|
|
839
839
|
|
|
@@ -910,7 +910,7 @@ import '@fsegurai/marked-extended-tabs/styles/tabs.css';
|
|
|
910
910
|
marked.use(markedExtendedTabs({
|
|
911
911
|
animation: 'fade',
|
|
912
912
|
autoActivate: true,
|
|
913
|
-
persistSelection:
|
|
913
|
+
persistSelection: false,
|
|
914
914
|
enableKeyboardNavigation: true,
|
|
915
915
|
enableFocusManagement: true,
|
|
916
916
|
|
|
@@ -1019,7 +1019,7 @@ import markedExtendedTabs from "@fsegurai/marked-extended-tabs";
|
|
|
1019
1019
|
marked.use(markedExtendedTabs({
|
|
1020
1020
|
className: 'my-custom-tabs',
|
|
1021
1021
|
animation: 'slide',
|
|
1022
|
-
persistSelection:
|
|
1022
|
+
persistSelection: false,
|
|
1023
1023
|
autoActivate: true,
|
|
1024
1024
|
customizeToken: (token) => {
|
|
1025
1025
|
// Add custom logic here
|
|
@@ -2315,6 +2315,7 @@ on [GitHub](https://github.com/fsegurai/marked-extensions/issues).
|
|
|
2315
2315
|
|
|
2316
2316
|
| Extension | Package | Version | Description |
|
|
2317
2317
|
|-------------|--------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|----------------------------------------------------------------------|
|
|
2318
|
+
| All - Bundle | [@fsegurai/marked-extended-bundle](https://www.npmjs.com/package/@fsegurai/marked-extended-bundle) |  | Includes all extensions in a single package for easy integration |
|
|
2318
2319
|
| Accordion | [@fsegurai/marked-extended-accordion](https://www.npmjs.com/package/@fsegurai/marked-extended-accordion) |  | Add collapsible accordion sections to your markdown |
|
|
2319
2320
|
| Alert | [@fsegurai/marked-extended-alert](https://www.npmjs.com/package/@fsegurai/marked-extended-alert) |  | Create styled alert boxes for important information |
|
|
2320
2321
|
| Comments | [@fsegurai/marked-extended-comments](https://www.npmjs.com/package/@fsegurai/marked-extended-comments) |  | Add comment sections with author and timestamp metadata |
|
package/dist/index.cjs
CHANGED
|
@@ -1,125 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Counter for generating unique IDs for tab containers
|
|
4
|
-
const tabCounter = { value: 0 };
|
|
5
|
-
/**
|
|
6
|
-
* Default configuration options for the extended tabs component.
|
|
7
|
-
*
|
|
8
|
-
* @constant {Object} DEFAULT_OPTIONS
|
|
9
|
-
* @property {string} className - The CSS class name applied to the tabs container.
|
|
10
|
-
* @property {boolean} persistSelection - Determines if the selection state should be persisted across sessions.
|
|
11
|
-
* @property {string} animation - The animation style for tab transitions, either 'fade', 'slide', or 'none'.
|
|
12
|
-
* @property {boolean} autoActivate - If true, the first tab is automatically activated when no tab is marked as active.
|
|
13
|
-
* @property {?string} template - A custom template for rendering the tabs, or null to use the default template.
|
|
14
|
-
* @property {?Function} customizeToken - A function for customizing tab-related tokens, or null for default handling.
|
|
15
|
-
*/
|
|
16
|
-
const DEFAULT_OPTIONS = {
|
|
17
|
-
className: 'marked-extended-tabs-container',
|
|
18
|
-
persistSelection: true,
|
|
19
|
-
animation: 'fade', // 'fade', 'slide', or 'none'
|
|
20
|
-
autoActivate: true, // Automatically activate first tab if none marked active
|
|
21
|
-
template: null,
|
|
22
|
-
customizeToken: null,
|
|
23
|
-
onBeforeSwitch: null,
|
|
24
|
-
onAfterSwitch: null,
|
|
25
|
-
enableKeyboardNavigation: true,
|
|
26
|
-
enableFocusManagement: true,
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* DEFAULT_TEMPLATE is a template string used to generate the HTML structure
|
|
30
|
-
* for a custom tab component. It contains placeholders that are dynamically
|
|
31
|
-
* replaced with actual values during runtime to customize the generated markup.
|
|
32
|
-
*
|
|
33
|
-
* Placeholders:
|
|
34
|
-
* - {tabsContainerId}: The unique identifier for the container of the tabs.
|
|
35
|
-
* - {className}: The CSS class names to be applied to the container.
|
|
36
|
-
* - {animation}: The data-animation attribute value for defining the animation type.
|
|
37
|
-
* - {inputsNav}: Additional HTML or elements used in the navigation section of the tabs.
|
|
38
|
-
* - {navList}: The list of navigation items for the tabs, rendered as part of the tablist.
|
|
39
|
-
* - {content}: The content to be displayed in the tabs' content area.
|
|
40
|
-
* - {stylesBehavior}: Inline styles or behaviors injected into the template.
|
|
41
|
-
*/
|
|
42
|
-
const DEFAULT_TEMPLATE = `
|
|
1
|
+
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e={value:0},t={className:`marked-extended-tabs-container`,persistSelection:!1,animation:`fade`,autoActivate:!0,template:null,customizeToken:null,onBeforeSwitch:null,onAfterSwitch:null,enableKeyboardNavigation:!0,enableFocusManagement:!0},n=`
|
|
43
2
|
<div id="{tabsContainerId}" class="{className}" data-animation="{animation}">
|
|
44
3
|
{inputsNav}
|
|
45
4
|
<div class="marked-extended-tabs-nav" role="tablist">{navList}</div>
|
|
46
5
|
<div class="marked-extended-tabs-content">{content}</div>
|
|
47
6
|
{stylesBehavior}
|
|
48
7
|
</div>
|
|
49
|
-
`;
|
|
50
|
-
/**
|
|
51
|
-
* Generates a CSS style string for tabs, defining how each tab is displayed and animated
|
|
52
|
-
* based on the provided container ID, tab data, and animation type.
|
|
53
|
-
*
|
|
54
|
-
* @param {string} tabsContainerId - The ID of the container element that wraps the tabs.
|
|
55
|
-
* @param {Array<{id: string}>} tabsData - An array of objects representing tabs,
|
|
56
|
-
* each object must have a unique `id`.
|
|
57
|
-
* @param {string} animation - The type of animation to apply when switching tabs.
|
|
58
|
-
* Acceptable values are 'fade', 'slide', or 'none'.
|
|
59
|
-
* @return {string} A string of CSS styles defining the behavior of the tabs and their animations.
|
|
60
|
-
*/
|
|
61
|
-
function createTabsStyles(tabsContainerId, tabsData, animation) {
|
|
62
|
-
const parts = [];
|
|
63
|
-
parts.push('<style>');
|
|
64
|
-
parts.push(' /* CSS-only tab selection - show selected tab */');
|
|
65
|
-
tabsData.forEach((tab) => {
|
|
66
|
-
const inputId = `input-${tab.id}`;
|
|
67
|
-
const labelId = `label-${tab.id}`;
|
|
68
|
-
const tabId = tab.id;
|
|
69
|
-
parts.push(`
|
|
8
|
+
`;function r(e,t,n){let r=[];return r.push(`<style>`),r.push(` /* CSS-only tab selection - show selected tab */`),t.forEach(t=>{let i=`input-${t.id}`,a=`label-${t.id}`,o=t.id;r.push(`
|
|
70
9
|
/* Show tab content when corresponding input is checked */
|
|
71
|
-
#${
|
|
10
|
+
#${e} #${i}:checked ~ .marked-extended-tabs-content #${o} {
|
|
72
11
|
display: block;
|
|
73
|
-
animation: ${
|
|
12
|
+
animation: ${n===`fade`?`tab-fade-in`:n===`slide`?`tab-slide-in`:`none`} 0.3s ease-in-out;
|
|
74
13
|
}
|
|
75
14
|
|
|
76
15
|
/* Style for active tab */
|
|
77
|
-
#${
|
|
16
|
+
#${e} #${i}:checked ~ .marked-extended-tabs-nav #${a} {
|
|
78
17
|
background: var(--marked-extended-tabs-selected-background) !important;
|
|
79
18
|
border-bottom-color: var(--marked-extended-tabs-selected-border-color) !important;
|
|
80
19
|
color: var(--marked-extended-tabs-selected-color) !important;
|
|
81
20
|
font-weight: 600 !important;
|
|
82
21
|
}
|
|
83
|
-
`)
|
|
84
|
-
});
|
|
85
|
-
if (animation === 'fade') {
|
|
86
|
-
parts.push(`@keyframes tab-fade-in {
|
|
22
|
+
`)}),n===`fade`&&r.push(`@keyframes tab-fade-in {
|
|
87
23
|
from { opacity: 0; }
|
|
88
24
|
to { opacity: 1; }
|
|
89
|
-
}`)
|
|
90
|
-
}
|
|
91
|
-
if (animation === 'slide') {
|
|
92
|
-
parts.push(`@keyframes tab-slide-in {
|
|
25
|
+
}`),n===`slide`&&r.push(`@keyframes tab-slide-in {
|
|
93
26
|
from { transform: translateY(10px); opacity: 0; }
|
|
94
27
|
to { transform: translateY(0); opacity: 1; }
|
|
95
|
-
}`)
|
|
96
|
-
|
|
97
|
-
parts.push('</style>');
|
|
98
|
-
return parts.join('\n');
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Generates JavaScript code for enhanced tab interaction features
|
|
102
|
-
* Includes keyboard navigation, focus management, and state persistence
|
|
103
|
-
*
|
|
104
|
-
* @param {string} tabsContainerId - The ID of the tabs container
|
|
105
|
-
* @param {Array<{id: string, props: {label: string}}>} tabsData - Array of tab data
|
|
106
|
-
* @param {boolean} enableKeyboardNavigation - Enable arrow key navigation
|
|
107
|
-
* @param {boolean} enableFocusManagement - Enable focus management
|
|
108
|
-
* @param {boolean} persistSelection - Enable local storage persistence
|
|
109
|
-
* @return {string} JavaScript code as a script tag
|
|
110
|
-
*/
|
|
111
|
-
function createTabsScript(tabsContainerId, tabsData, enableKeyboardNavigation = true, enableFocusManagement = true, persistSelection = true) {
|
|
112
|
-
const storageKey = `marked-extended-tabs-active-${tabsContainerId}`;
|
|
113
|
-
const js = `
|
|
28
|
+
}`),r.push(`</style>`),r.join(`
|
|
29
|
+
`)}function i(e,t,n=!0,r=!0,i=!1){return`<script>
|
|
114
30
|
/* eslint-disable no-unused-vars */
|
|
115
31
|
(function() {
|
|
116
|
-
const containerId = '${
|
|
117
|
-
const storageKey = '${
|
|
32
|
+
const containerId = '${e}';
|
|
33
|
+
const storageKey = '${`marked-extended-tabs-active-${e}`}';
|
|
118
34
|
// @ts-ignore - tabIds is used in the script template
|
|
119
|
-
const tabIds = ${JSON.stringify(
|
|
120
|
-
const enableKeyboardNav = ${
|
|
121
|
-
const enableFocusManagement = ${
|
|
122
|
-
const persistSelection = ${
|
|
35
|
+
const tabIds = ${JSON.stringify(t.map(e=>e.id))};
|
|
36
|
+
const enableKeyboardNav = ${n};
|
|
37
|
+
const enableFocusManagement = ${r};
|
|
38
|
+
const persistSelection = ${i};
|
|
123
39
|
|
|
124
40
|
// Function to update aria-selected state
|
|
125
41
|
function updateAriaSelected(container, activeInputId) {
|
|
@@ -334,379 +250,10 @@ function createTabsScript(tabsContainerId, tabsData, enableKeyboardNavigation =
|
|
|
334
250
|
} else {
|
|
335
251
|
initializeTabs();
|
|
336
252
|
}
|
|
337
|
-
})()
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
*/
|
|
345
|
-
const propRegex = /\s*(\w+)="([^"]+)"/g;
|
|
346
|
-
/**
|
|
347
|
-
* Element patterns configuration for tabs blocks
|
|
348
|
-
*/
|
|
349
|
-
const elementPatterns = {
|
|
350
|
-
tabBlock: {
|
|
351
|
-
start: '::::tabs',
|
|
352
|
-
end: '::::tabsend',
|
|
353
|
-
aliases: [':tbs', ':tabs'],
|
|
354
|
-
endAliases: [':tbsend', ':tabsend'],
|
|
355
|
-
},
|
|
356
|
-
tabItemBlock: {
|
|
357
|
-
start: ':::tab',
|
|
358
|
-
end: ':::tabend',
|
|
359
|
-
aliases: [':tab'],
|
|
360
|
-
endAliases: [':tabend'],
|
|
361
|
-
},
|
|
362
|
-
};
|
|
363
|
-
/**
|
|
364
|
-
* Supported properties by element type
|
|
365
|
-
*/
|
|
366
|
-
const supportedPropsByElement = {
|
|
367
|
-
tabItemBlock: [
|
|
368
|
-
{ name: 'label', defaultValue: '' },
|
|
369
|
-
{ name: 'active', defaultValue: 'false' },
|
|
370
|
-
{ name: 'icon', defaultValue: null },
|
|
371
|
-
],
|
|
372
|
-
};
|
|
373
|
-
/**
|
|
374
|
-
* Parses balanced opening and closing tags with proper nesting support
|
|
375
|
-
* @param src - Source string to parse
|
|
376
|
-
* @param elementType - Type of element to parse
|
|
377
|
-
* @returns RegExpExecArray or null if no match
|
|
378
|
-
*/
|
|
379
|
-
function parseBalancedTags(src, elementType) {
|
|
380
|
-
const pattern = elementPatterns[elementType];
|
|
381
|
-
if (!pattern)
|
|
382
|
-
return null;
|
|
383
|
-
const { start, end, aliases = [], endAliases = [] } = pattern;
|
|
384
|
-
// Find which start pattern matches
|
|
385
|
-
let matchedStart = '';
|
|
386
|
-
if (src.startsWith(start)) {
|
|
387
|
-
matchedStart = start;
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
for (const alias of aliases) {
|
|
391
|
-
if (src.startsWith(alias)) {
|
|
392
|
-
matchedStart = alias;
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (!matchedStart)
|
|
398
|
-
return null;
|
|
399
|
-
const startPos = matchedStart.length;
|
|
400
|
-
// Parse property block (optional)
|
|
401
|
-
let propString = '';
|
|
402
|
-
let contentStart = startPos;
|
|
403
|
-
// Skip whitespace and check for property block
|
|
404
|
-
let scanPos = startPos;
|
|
405
|
-
while (scanPos < src.length && src[scanPos] === ' ') {
|
|
406
|
-
scanPos++;
|
|
407
|
-
}
|
|
408
|
-
if (scanPos < src.length && src[scanPos] === '{') {
|
|
409
|
-
const propEnd = src.indexOf('}', scanPos);
|
|
410
|
-
if (propEnd !== -1) {
|
|
411
|
-
propString = src.substring(startPos, propEnd);
|
|
412
|
-
contentStart = propEnd + 1;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
// Parse nested content with depth tracking
|
|
416
|
-
let depth = 1;
|
|
417
|
-
let pos = contentStart;
|
|
418
|
-
while (pos < src.length && depth > 0) {
|
|
419
|
-
// Find next start tag
|
|
420
|
-
const mainStartPos = src.indexOf(start, pos);
|
|
421
|
-
let nextStartPos = mainStartPos;
|
|
422
|
-
for (const alias of aliases) {
|
|
423
|
-
const aliasPos = src.indexOf(alias, pos);
|
|
424
|
-
if (aliasPos !== -1 && (nextStartPos === -1 || aliasPos < nextStartPos)) {
|
|
425
|
-
nextStartPos = aliasPos;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
// Find next end tag
|
|
429
|
-
let foundEndPattern = end;
|
|
430
|
-
let nextEndPos = src.indexOf(end, pos);
|
|
431
|
-
for (const endAlias of endAliases) {
|
|
432
|
-
const endAliasPos = src.indexOf(endAlias, pos);
|
|
433
|
-
if (endAliasPos !== -1 && (nextEndPos === -1 || endAliasPos < nextEndPos)) {
|
|
434
|
-
nextEndPos = endAliasPos;
|
|
435
|
-
foundEndPattern = endAlias;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
if (nextEndPos === -1)
|
|
439
|
-
return null;
|
|
440
|
-
// Handle nested tags
|
|
441
|
-
if (nextStartPos !== -1 && nextStartPos < nextEndPos) {
|
|
442
|
-
depth++;
|
|
443
|
-
let matchedPattern = start;
|
|
444
|
-
if (nextStartPos === mainStartPos) {
|
|
445
|
-
matchedPattern = start;
|
|
446
|
-
}
|
|
447
|
-
else {
|
|
448
|
-
for (const alias of aliases) {
|
|
449
|
-
if (src.indexOf(alias, pos) === nextStartPos) {
|
|
450
|
-
matchedPattern = alias;
|
|
451
|
-
break;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
pos = nextStartPos + matchedPattern.length;
|
|
456
|
-
continue;
|
|
457
|
-
}
|
|
458
|
-
// Check if we've closed all nested tags
|
|
459
|
-
if (--depth === 0) {
|
|
460
|
-
const content = src.substring(contentStart, nextEndPos);
|
|
461
|
-
const fullMatch = src.substring(0, nextEndPos + foundEndPattern.length);
|
|
462
|
-
const result = [fullMatch, propString, content];
|
|
463
|
-
result.index = 0;
|
|
464
|
-
result.input = src;
|
|
465
|
-
return result;
|
|
466
|
-
}
|
|
467
|
-
pos = nextEndPos + foundEndPattern.length;
|
|
468
|
-
}
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Validates and parses element patterns from source string
|
|
473
|
-
* @param element - Element type or RegExp to validate
|
|
474
|
-
* @param src - Source string to parse
|
|
475
|
-
* @returns RegExpExecArray or null if no match
|
|
476
|
-
*/
|
|
477
|
-
const validateRegex = (element, src) => {
|
|
478
|
-
if (element instanceof RegExp) {
|
|
479
|
-
return element.exec(src);
|
|
480
|
-
}
|
|
481
|
-
switch (element) {
|
|
482
|
-
case 'tabBlock':
|
|
483
|
-
case 'tabItemBlock':
|
|
484
|
-
return parseBalancedTags(src, element);
|
|
485
|
-
default:
|
|
486
|
-
throw new Error(`Unknown element: ${element}`);
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
/**
|
|
490
|
-
* Constructs properties object from property string
|
|
491
|
-
* @param element - Element type
|
|
492
|
-
* @param propString - String containing properties
|
|
493
|
-
* @returns Record of property names to values
|
|
494
|
-
*/
|
|
495
|
-
const constructProps = (element, propString) => {
|
|
496
|
-
const supportedProps = supportedPropsByElement[element];
|
|
497
|
-
if (!supportedProps) {
|
|
498
|
-
throw new Error(`Unknown element: ${element}`);
|
|
499
|
-
}
|
|
500
|
-
// Initialize with default values
|
|
501
|
-
const props = {};
|
|
502
|
-
supportedProps.forEach((prop) => {
|
|
503
|
-
props[prop.name] = prop.defaultValue;
|
|
504
|
-
});
|
|
505
|
-
// Parse and override with actual values
|
|
506
|
-
propRegex.lastIndex = 0;
|
|
507
|
-
let propMatch;
|
|
508
|
-
while ((propMatch = propRegex.exec(propString)) !== null) {
|
|
509
|
-
const [, name, value] = propMatch;
|
|
510
|
-
if (supportedProps.some((p) => p.name === name)) {
|
|
511
|
-
props[name] = value;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
return props;
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Renders the tab component with enhanced interactivity
|
|
519
|
-
* @param options - Rendering options
|
|
520
|
-
* @param parser - Parser instance from marked.js renderer context
|
|
521
|
-
* @returns HTML string for the tabs
|
|
522
|
-
*/
|
|
523
|
-
function renderTabs(options, parser) {
|
|
524
|
-
const { tabsContainerId, tabsData, className, animation = 'fade', template, enableKeyboardNavigation = true, enableFocusManagement = true, persistSelection, } = options;
|
|
525
|
-
// Get the tab template
|
|
526
|
-
const tabsTemplate = template || DEFAULT_TEMPLATE;
|
|
527
|
-
if (!tabsData || tabsData.length === 0) {
|
|
528
|
-
return '<div class="error-message">No tab content found</div>';
|
|
529
|
-
}
|
|
530
|
-
let inputsNav = '';
|
|
531
|
-
let navList = '';
|
|
532
|
-
let markedContent = '';
|
|
533
|
-
for (const tab of tabsData) {
|
|
534
|
-
const { id, tokens, props } = tab;
|
|
535
|
-
const { active, icon, label } = props;
|
|
536
|
-
const inputId = `input-${id}`;
|
|
537
|
-
const labelId = `label-${id}`;
|
|
538
|
-
const isChecked = active ? 'checked' : '';
|
|
539
|
-
const ariaSelected = active ? 'true' : 'false';
|
|
540
|
-
const iconHtml = icon ? `<span class="marked-extended-tabs-icon">${icon}</span>` : '';
|
|
541
|
-
// Inputs first
|
|
542
|
-
inputsNav += `<input type="radio" name="${tabsContainerId}-tabs" id="${inputId}" class="marked-extended-tabs-input" ${isChecked}>`;
|
|
543
|
-
// Navigation labels with data attributes for JavaScript enhancement
|
|
544
|
-
navList += `
|
|
545
|
-
<label for="${inputId}" id="${labelId}" class="marked-extended-tabs-label" role="tab" aria-selected="${ariaSelected}" data-tab-id="${id}" tabindex="${active ? '0' : '-1'}">
|
|
546
|
-
${iconHtml}<span class="tab-label">${label}</span>
|
|
547
|
-
</label>`;
|
|
548
|
-
// Render the content using the parser context which has all extensions loaded
|
|
549
|
-
// This allows nested extensions to be properly rendered
|
|
550
|
-
const parsedContent = tokens && tokens.length > 0 ? parser.parse(tokens) : '';
|
|
551
|
-
// Tab content
|
|
552
|
-
markedContent += `
|
|
553
|
-
<div class="marked-extended-tabs-content-pane" id="${id}" role="tabpanel" aria-labelledby="${inputId}">
|
|
554
|
-
${parsedContent}
|
|
555
|
-
</div>`;
|
|
556
|
-
}
|
|
557
|
-
const styles = createTabsStyles(tabsContainerId, tabsData, animation);
|
|
558
|
-
// Generate JavaScript for enhanced tab switching
|
|
559
|
-
const script = createTabsScript(tabsContainerId, tabsData, enableKeyboardNavigation, enableFocusManagement, persistSelection);
|
|
560
|
-
return tabsTemplate
|
|
561
|
-
.replace(/{tabsContainerId}/g, tabsContainerId)
|
|
562
|
-
.replace(/{className}/g, className)
|
|
563
|
-
.replace(/{animation}/g, animation)
|
|
564
|
-
.replace(/{inputsNav}/g, inputsNav)
|
|
565
|
-
.replace(/{navList}/g, navList)
|
|
566
|
-
.replace(/{content}/g, markedContent)
|
|
567
|
-
.replace(/{stylesBehavior}/g, styles + script);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Creates a tokenizer function for the tab extension
|
|
572
|
-
*/
|
|
573
|
-
function createTokenizer(options) {
|
|
574
|
-
const { animation, autoActivate, className, persistSelection, template, onBeforeSwitch, onAfterSwitch, enableKeyboardNavigation, enableFocusManagement } = options;
|
|
575
|
-
const tabElement = 'tabBlock';
|
|
576
|
-
const tabItemElement = 'tabItemBlock';
|
|
577
|
-
return {
|
|
578
|
-
name: 'tabs',
|
|
579
|
-
level: 'block',
|
|
580
|
-
tokenizer(src) {
|
|
581
|
-
const blockMatch = validateRegex(tabElement, src);
|
|
582
|
-
if (!blockMatch)
|
|
583
|
-
return undefined;
|
|
584
|
-
// The first position is the full match, the second is props, the third is content
|
|
585
|
-
const [raw, , tabContent] = blockMatch;
|
|
586
|
-
// Generate unique ID for this tab container
|
|
587
|
-
const tabsContainerId = `tabs-container-${++tabCounter.value}`;
|
|
588
|
-
// Find individual tabs directly in the extracted tab content
|
|
589
|
-
const items = [];
|
|
590
|
-
let tabIndex = 0;
|
|
591
|
-
let remainingContent = tabContent;
|
|
592
|
-
while (remainingContent) {
|
|
593
|
-
// Skip whitespace
|
|
594
|
-
remainingContent = remainingContent.trimStart();
|
|
595
|
-
if (!remainingContent)
|
|
596
|
-
break;
|
|
597
|
-
const tabMatch = validateRegex(tabItemElement, remainingContent);
|
|
598
|
-
if (!tabMatch) {
|
|
599
|
-
// If we have content but it doesn't match a tab item, strictly speaking this might be invalid content
|
|
600
|
-
// inside a tabs container, or we could just breaking to avoid infinite loop.
|
|
601
|
-
break;
|
|
602
|
-
}
|
|
603
|
-
const [itemRaw, propsStr, tabContentStr] = tabMatch;
|
|
604
|
-
// Parse properties with defaults
|
|
605
|
-
const rawProps = constructProps(tabItemElement, propsStr);
|
|
606
|
-
// If no label is provided, use a default
|
|
607
|
-
const label = rawProps['label'] || `Tab ${tabIndex + 1}`;
|
|
608
|
-
// Convert string 'true'/'false' to boolean for the active property
|
|
609
|
-
const active = rawProps['active'] === 'true';
|
|
610
|
-
// Tokenize the tab content using this.lexer.blockTokens
|
|
611
|
-
// This ensures nested extensions are properly tokenized
|
|
612
|
-
const innerTokens = this.lexer.blockTokens(tabContentStr.trim());
|
|
613
|
-
// Add to tabs data - include raw so it satisfies Tokens.Generic
|
|
614
|
-
items.push({
|
|
615
|
-
type: 'tab-item',
|
|
616
|
-
raw: itemRaw,
|
|
617
|
-
id: `${tabsContainerId}-tab-${tabIndex}`,
|
|
618
|
-
props: {
|
|
619
|
-
label,
|
|
620
|
-
active,
|
|
621
|
-
icon: rawProps['icon'] || undefined,
|
|
622
|
-
},
|
|
623
|
-
tokens: innerTokens,
|
|
624
|
-
});
|
|
625
|
-
tabIndex++;
|
|
626
|
-
// Advance cursor
|
|
627
|
-
remainingContent = remainingContent.substring(itemRaw.length);
|
|
628
|
-
}
|
|
629
|
-
// If no tabs were found, return undefined
|
|
630
|
-
if (items.length === 0)
|
|
631
|
-
return undefined;
|
|
632
|
-
// Apply auto-activation if enabled, and no tab is explicitly marked as active
|
|
633
|
-
if (autoActivate) {
|
|
634
|
-
const hasActiveTab = items.some((tab) => tab.props.active);
|
|
635
|
-
if (!hasActiveTab && items.length > 0) {
|
|
636
|
-
items[0].props.active = true;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
// Create a token with metadata for the renderer
|
|
640
|
-
return {
|
|
641
|
-
type: 'tabs',
|
|
642
|
-
raw,
|
|
643
|
-
tokens: items,
|
|
644
|
-
meta: {
|
|
645
|
-
tabsContainerId,
|
|
646
|
-
className,
|
|
647
|
-
persistSelection,
|
|
648
|
-
animation,
|
|
649
|
-
autoActivate,
|
|
650
|
-
template,
|
|
651
|
-
onBeforeSwitch,
|
|
652
|
-
onAfterSwitch,
|
|
653
|
-
enableKeyboardNavigation,
|
|
654
|
-
enableFocusManagement,
|
|
655
|
-
},
|
|
656
|
-
};
|
|
657
|
-
},
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* Create the renderer extension for tabs
|
|
662
|
-
*/
|
|
663
|
-
function createRenderer() {
|
|
664
|
-
return {
|
|
665
|
-
name: 'tabs',
|
|
666
|
-
renderer(token) {
|
|
667
|
-
const tabsToken = token;
|
|
668
|
-
// Pass meta and parser to the renderer for proper nested extension support
|
|
669
|
-
return renderTabs({ ...tabsToken.meta, tabsData: tabsToken.tokens }, this.parser);
|
|
670
|
-
},
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* Marked Extended Tabs extension
|
|
676
|
-
* @param options - Configuration options
|
|
677
|
-
* @returns Marked extension object
|
|
678
|
-
*/
|
|
679
|
-
function markedExtendedTabs(options = {}) {
|
|
680
|
-
// Validate animation option
|
|
681
|
-
if (options.animation && !['fade', 'slide', 'none'].includes(options.animation)) {
|
|
682
|
-
console.warn(`[marked-extended-tabs] Invalid animation value: ${options.animation}. Using default 'fade'.`);
|
|
683
|
-
options.animation = 'fade';
|
|
684
|
-
}
|
|
685
|
-
// Validate callbacks
|
|
686
|
-
if (options.onBeforeSwitch && typeof options.onBeforeSwitch !== 'function') {
|
|
687
|
-
console.warn('[marked-extended-tabs] onBeforeSwitch must be a function');
|
|
688
|
-
options.onBeforeSwitch = null;
|
|
689
|
-
}
|
|
690
|
-
if (options.onAfterSwitch && typeof options.onAfterSwitch !== 'function') {
|
|
691
|
-
console.warn('[marked-extended-tabs] onAfterSwitch must be a function');
|
|
692
|
-
options.onAfterSwitch = null;
|
|
693
|
-
}
|
|
694
|
-
// Set sensible defaults
|
|
695
|
-
const config = { ...DEFAULT_OPTIONS, ...options };
|
|
696
|
-
// NOTE: Styles are no longer injected automatically. Import the CSS/SCSS files manually in your project.
|
|
697
|
-
// Return the extension
|
|
698
|
-
return {
|
|
699
|
-
walkTokens(token) {
|
|
700
|
-
if (token.type !== 'tabs')
|
|
701
|
-
return;
|
|
702
|
-
// Apply custom token modifications if configured
|
|
703
|
-
if (config.customizeToken && typeof config.customizeToken === 'function') {
|
|
704
|
-
config.customizeToken(token);
|
|
705
|
-
}
|
|
706
|
-
},
|
|
707
|
-
extensions: [createTokenizer(config), createRenderer()],
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
module.exports = markedExtendedTabs;
|
|
712
|
-
//# sourceMappingURL=index.cjs.map
|
|
253
|
+
})();<\/script>`}var a=/\s*(\w+)="([^"]+)"/g,o={tabBlock:{start:`::::tabs`,end:`::::tabsend`,aliases:[`:tbs`,`:tabs`],endAliases:[`:tbsend`,`:tabsend`]},tabItemBlock:{start:`:::tab`,end:`:::tabend`,aliases:[`:tab`],endAliases:[`:tabend`]}},s={tabItemBlock:[{name:`label`,defaultValue:``},{name:`active`,defaultValue:`false`},{name:`icon`,defaultValue:null}]};function c(e,t){let n=o[t];if(!n)return null;let{start:r,end:i,aliases:a=[],endAliases:s=[]}=n,c=``;if(e.startsWith(r))c=r;else for(let t of a)if(e.startsWith(t)){c=t;break}if(!c)return null;let l=c.length,u=``,d=l,f=l;for(;f<e.length&&e[f]===` `;)f++;if(f<e.length&&e[f]===`{`){let t=e.indexOf(`}`,f);t!==-1&&(u=e.substring(l,t),d=t+1)}let p=1,m=d;for(;m<e.length&&p>0;){let t=e.indexOf(r,m),n=t;for(let t of a){let r=e.indexOf(t,m);r!==-1&&(n===-1||r<n)&&(n=r)}let o=i,c=e.indexOf(i,m);for(let t of s){let n=e.indexOf(t,m);n!==-1&&(c===-1||n<c)&&(c=n,o=t)}if(c===-1)return null;if(n!==-1&&n<c){p++;let i=r;if(n===t)i=r;else for(let t of a)if(e.indexOf(t,m)===n){i=t;break}m=n+i.length;continue}if(--p===0){let t=e.substring(d,c),n=[e.substring(0,c+o.length),u,t];return n.index=0,n.input=e,n}m=c+o.length}return null}var l=(e,t)=>{if(e instanceof RegExp)return e.exec(t);switch(e){case`tabBlock`:case`tabItemBlock`:return c(t,e);default:throw Error(`Unknown element: ${e}`)}},u=(e,t)=>{let n=s[e];if(!n)throw Error(`Unknown element: ${e}`);let r={};n.forEach(e=>{r[e.name]=e.defaultValue}),a.lastIndex=0;let i;for(;(i=a.exec(t))!==null;){let[,e,t]=i;n.some(t=>t.name===e)&&(r[e]=t)}return r};function d(e,t){let{tabsContainerId:a,tabsData:o,className:s,animation:c=`fade`,template:l,enableKeyboardNavigation:u=!0,enableFocusManagement:d=!0,persistSelection:f}=e,p=l||n;if(!o||o.length===0)return`<div class="error-message">No tab content found</div>`;let m=``,h=``,g=``;for(let e of o){let{id:n,tokens:r,props:i}=e,{active:o,icon:s,label:c}=i,l=`input-${n}`,u=`label-${n}`,d=o?`checked`:``,f=o?`true`:`false`,p=s?`<span class="marked-extended-tabs-icon">${s}</span>`:``;m+=`<input type="radio" name="${a}-tabs" id="${l}" class="marked-extended-tabs-input" ${d}>`,h+=`
|
|
254
|
+
<label for="${l}" id="${u}" class="marked-extended-tabs-label" role="tab" aria-selected="${f}" data-tab-id="${n}" tabindex="${o?`0`:`-1`}">
|
|
255
|
+
${p}<span class="tab-label">${c}</span>
|
|
256
|
+
</label>`;let _=r&&r.length>0?t.parse(r):``;g+=`
|
|
257
|
+
<div class="marked-extended-tabs-content-pane" id="${n}" role="tabpanel" aria-labelledby="${l}">
|
|
258
|
+
${_}
|
|
259
|
+
</div>`}let _=r(a,o,c),v=i(a,o,u,d,f);return p.replace(/{tabsContainerId}/g,a).replace(/{className}/g,s).replace(/{animation}/g,c).replace(/{inputsNav}/g,m).replace(/{navList}/g,h).replace(/{content}/g,g).replace(/{stylesBehavior}/g,_+v)}function f(t){let{animation:n,autoActivate:r,className:i,persistSelection:a,template:o,onBeforeSwitch:s,onAfterSwitch:c,enableKeyboardNavigation:d,enableFocusManagement:f}=t,p=`tabItemBlock`;return{name:`tabs`,level:`block`,tokenizer(t){let m=l(`tabBlock`,t);if(!m)return;let[h,,g]=m,_=`tabs-container-${++e.value}`,v=[],y=0,b=g;for(;b&&(b=b.trimStart(),b);){let e=l(p,b);if(!e)break;let[t,n,r]=e,i=u(p,n),a=i.label||`Tab ${y+1}`,o=i.active===`true`,s=this.lexer.blockTokens(r.trim());v.push({type:`tab-item`,raw:t,id:`${_}-tab-${y}`,props:{label:a,active:o,icon:i.icon||void 0},tokens:s}),y++,b=b.substring(t.length)}if(v.length!==0)return r&&!v.some(e=>e.props.active)&&v.length>0&&(v[0].props.active=!0),{type:`tabs`,raw:h,tokens:v,meta:{tabsContainerId:_,className:i,persistSelection:a,animation:n,autoActivate:r,template:o,onBeforeSwitch:s,onAfterSwitch:c,enableKeyboardNavigation:d,enableFocusManagement:f}}}}}function p(){return{name:`tabs`,renderer(e){let t=e;return d({...t.meta,tabsData:t.tokens},this.parser)}}}function m(e={}){e.animation&&![`fade`,`slide`,`none`].includes(e.animation)&&(console.warn(`[marked-extended-tabs] Invalid animation value: ${e.animation}. Using default 'fade'.`),e.animation=`fade`),e.onBeforeSwitch&&typeof e.onBeforeSwitch!=`function`&&(console.warn(`[marked-extended-tabs] onBeforeSwitch must be a function`),e.onBeforeSwitch=null),e.onAfterSwitch&&typeof e.onAfterSwitch!=`function`&&(console.warn(`[marked-extended-tabs] onAfterSwitch must be a function`),e.onAfterSwitch=null);let n={...t,...e};return{walkTokens(e){e.type===`tabs`&&n.customizeToken&&typeof n.customizeToken==`function`&&n.customizeToken(e)},extensions:[f(n),p()]}}exports.default=m;
|