juxscript 1.1.301 → 1.1.302

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.
@@ -0,0 +1,152 @@
1
+ import generateId from '../utils/idgen.js';
2
+ import { pageState } from '../state/pageState.js';
3
+ class Nav {
4
+ constructor(id, options = {}) {
5
+ this._items = [];
6
+ this._value = null;
7
+ this._onChange = null;
8
+ this.id = id || generateId();
9
+ this.opts = {
10
+ orientation: 'vertical',
11
+ activeClass: 'jux-nav-item--active',
12
+ itemClass: 'jux-nav-item',
13
+ ...options
14
+ };
15
+ this._element = document.createElement('nav');
16
+ this._element.id = this.id;
17
+ if (this.opts.class)
18
+ this._element.className = this.opts.class;
19
+ if (this.opts.style)
20
+ this._element.setAttribute('style', this.opts.style);
21
+ this._element.setAttribute('data-orientation', this.opts.orientation);
22
+ this._element.setAttribute('role', 'navigation');
23
+ const resolvedTarget = this.opts.target;
24
+ const container = resolvedTarget
25
+ ? document.getElementById(resolvedTarget) || document.querySelector(resolvedTarget)
26
+ : document.getElementById('app');
27
+ container?.appendChild(this._element);
28
+ if (this.opts.items) {
29
+ this.addItems(this.opts.items);
30
+ }
31
+ }
32
+ // ═══════════════════════════════════════════════════════════
33
+ // STANDARD COLLECTION API
34
+ // ═══════════════════════════════════════════════════════════
35
+ addItem(item) {
36
+ const navItem = { ...item, id: item.id || `${this.id}-item-${this._items.length}` };
37
+ this._items.push(navItem);
38
+ this._renderItem(navItem);
39
+ this._dispatchChange();
40
+ return this;
41
+ }
42
+ addItems(items) {
43
+ for (const item of items) {
44
+ const navItem = { ...item, id: item.id || `${this.id}-item-${this._items.length}` };
45
+ this._items.push(navItem);
46
+ this._renderItem(navItem);
47
+ }
48
+ this._dispatchChange();
49
+ return this;
50
+ }
51
+ removeItem(key) {
52
+ const idx = typeof key === 'number' ? key : this._items.findIndex(i => i.id === key);
53
+ if (idx > -1) {
54
+ this._items.splice(idx, 1);
55
+ this._renderAll();
56
+ this._dispatchChange();
57
+ }
58
+ return this;
59
+ }
60
+ updateItem(key, updates) {
61
+ const idx = typeof key === 'number' ? key : this._items.findIndex(i => i.id === key);
62
+ if (idx > -1) {
63
+ this._items[idx] = { ...this._items[idx], ...updates };
64
+ this._renderAll();
65
+ this._dispatchChange();
66
+ }
67
+ return this;
68
+ }
69
+ clearItems() {
70
+ this._items = [];
71
+ this._value = null;
72
+ this._element.innerHTML = '';
73
+ this._dispatchChange();
74
+ return this;
75
+ }
76
+ getItems() { return [...this._items]; }
77
+ getCount() { return this._items.length; }
78
+ // ═══════════════════════════════════════════════════════════
79
+ // PAGESTATE INTEGRATION
80
+ // ═══════════════════════════════════════════════════════════
81
+ getValue() { return this._value; }
82
+ setValue(val) {
83
+ this._value = val;
84
+ this._updateActiveState();
85
+ this._dispatchChange();
86
+ return this;
87
+ }
88
+ getElement() { return this._element; }
89
+ onChange(fn) {
90
+ this._onChange = fn;
91
+ return this;
92
+ }
93
+ // ═══════════════════════════════════════════════════════════
94
+ // INTERNAL
95
+ // ═══════════════════════════════════════════════════════════
96
+ _renderItem(item) {
97
+ const el = document.createElement('a');
98
+ el.id = item.id;
99
+ el.className = this.opts.itemClass || 'jux-nav-item';
100
+ el.textContent = item.label;
101
+ el.setAttribute('role', 'link');
102
+ if (item.path)
103
+ el.setAttribute('data-path', item.path);
104
+ if (item.disabled)
105
+ el.setAttribute('data-disabled', 'true');
106
+ if (item.icon)
107
+ el.setAttribute('data-icon', item.icon);
108
+ el.addEventListener('click', (e) => {
109
+ e.preventDefault();
110
+ if (item.disabled)
111
+ return;
112
+ this._value = item.path || item.id;
113
+ this._updateActiveState();
114
+ if (this._onChange)
115
+ this._onChange(this._value);
116
+ this._element.dispatchEvent(new Event('change', { bubbles: false }));
117
+ });
118
+ this._element.appendChild(el);
119
+ }
120
+ _renderAll() {
121
+ this._element.innerHTML = '';
122
+ for (const item of this._items) {
123
+ this._renderItem(item);
124
+ }
125
+ this._updateActiveState();
126
+ }
127
+ _updateActiveState() {
128
+ const children = this._element.querySelectorAll('[role="link"]');
129
+ children.forEach(el => {
130
+ const path = el.getAttribute('data-path') || el.id;
131
+ if (path === this._value) {
132
+ el.classList.add(this.opts.activeClass);
133
+ el.setAttribute('data-active', 'true');
134
+ }
135
+ else {
136
+ el.classList.remove(this.opts.activeClass);
137
+ el.removeAttribute('data-active');
138
+ }
139
+ });
140
+ }
141
+ _dispatchChange() {
142
+ this._element.setAttribute('data-count', String(this._items.length));
143
+ this._element.dispatchEvent(new Event('change', { bubbles: false }));
144
+ }
145
+ }
146
+ export function nav(id, options = {}) {
147
+ const n = new Nav(id, options);
148
+ pageState.__register(n);
149
+ return n;
150
+ }
151
+ export { Nav };
152
+ export default nav;
@@ -0,0 +1,56 @@
1
+ interface TableColumn {
2
+ key: string;
3
+ label: string;
4
+ width?: string;
5
+ align?: 'left' | 'center' | 'right';
6
+ render?: (value: any, row: any) => string;
7
+ }
8
+ interface TableOptions {
9
+ columns?: TableColumn[];
10
+ items?: any[];
11
+ class?: string;
12
+ style?: string;
13
+ target?: string;
14
+ striped?: boolean;
15
+ hoverable?: boolean;
16
+ selectable?: boolean;
17
+ headerClass?: string;
18
+ rowClass?: string;
19
+ cellClass?: string;
20
+ }
21
+ declare class Table {
22
+ id: string;
23
+ opts: TableOptions;
24
+ private _element;
25
+ private _table;
26
+ private _thead;
27
+ private _tbody;
28
+ private _items;
29
+ private _columns;
30
+ private _value;
31
+ private _selectedIndex;
32
+ private _onChange;
33
+ constructor(id: string, options?: TableOptions);
34
+ setColumns(columns: TableColumn[]): this;
35
+ getColumns(): TableColumn[];
36
+ addItem(item: any): this;
37
+ addItems(items: any[]): this;
38
+ removeItem(key: string | number): this;
39
+ updateItem(key: string | number, updates: any): this;
40
+ clearItems(): this;
41
+ getItems(): any[];
42
+ getCount(): number;
43
+ getValue(): any | null;
44
+ setValue(val: any): this;
45
+ getElement(): HTMLElement;
46
+ onChange(fn: (value: any) => void): this;
47
+ private _autoColumns;
48
+ private _renderHeader;
49
+ private _renderBody;
50
+ private _highlightSelected;
51
+ private _dispatchChange;
52
+ }
53
+ export declare function table(id: string, options?: TableOptions): Table;
54
+ export { Table, TableColumn, TableOptions };
55
+ export default table;
56
+ //# sourceMappingURL=table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../lib/components/table.ts"],"names":[],"mappings":"AAGA,UAAU,WAAW;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC;CAC7C;AAED,UAAU,YAAY;IAClB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,cAAM,KAAK;IACP,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,SAAS,CAAuC;gBAE5C,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB;IA8ClD,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI;IAOxC,UAAU,IAAI,WAAW,EAAE;IAM3B,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAQxB,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,IAAI;IAU5B,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAUtC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI;IAUpD,UAAU,IAAI,IAAI;IASlB,QAAQ,IAAI,GAAG,EAAE;IACjB,QAAQ,IAAI,MAAM;IAMlB,QAAQ,IAAI,GAAG,GAAG,IAAI;IAEtB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAKxB,UAAU,IAAI,WAAW;IAEzB,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IASxC,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,WAAW;IAuCnB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,eAAe;CAI1B;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,KAAK,CAInE;AAED,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAC5C,eAAe,KAAK,CAAC"}
@@ -0,0 +1,199 @@
1
+ import generateId from '../utils/idgen.js';
2
+ import { pageState } from '../state/pageState.js';
3
+ class Table {
4
+ constructor(id, options = {}) {
5
+ this._items = [];
6
+ this._columns = [];
7
+ this._value = null;
8
+ this._selectedIndex = -1;
9
+ this._onChange = null;
10
+ this.id = id || generateId();
11
+ this.opts = {
12
+ striped: false,
13
+ hoverable: true,
14
+ selectable: false,
15
+ ...options
16
+ };
17
+ // Wrapper
18
+ this._element = document.createElement('div');
19
+ this._element.id = this.id;
20
+ this._element.setAttribute('data-jux-table', '');
21
+ if (this.opts.class)
22
+ this._element.className = this.opts.class;
23
+ if (this.opts.style)
24
+ this._element.setAttribute('style', this.opts.style);
25
+ // Table
26
+ this._table = document.createElement('table');
27
+ this._table.className = 'jux-table';
28
+ this._table.setAttribute('role', 'table');
29
+ this._thead = document.createElement('thead');
30
+ this._tbody = document.createElement('tbody');
31
+ this._table.appendChild(this._thead);
32
+ this._table.appendChild(this._tbody);
33
+ this._element.appendChild(this._table);
34
+ const resolvedTarget = this.opts.target;
35
+ const container = resolvedTarget
36
+ ? document.getElementById(resolvedTarget) || document.querySelector(resolvedTarget)
37
+ : document.getElementById('app');
38
+ container?.appendChild(this._element);
39
+ if (this.opts.columns) {
40
+ this._columns = this.opts.columns;
41
+ this._renderHeader();
42
+ }
43
+ if (this.opts.items) {
44
+ this.addItems(this.opts.items);
45
+ }
46
+ }
47
+ // ═══════════════════════════════════════════════════════════
48
+ // COLUMNS
49
+ // ═══════════════════════════════════════════════════════════
50
+ setColumns(columns) {
51
+ this._columns = columns;
52
+ this._renderHeader();
53
+ this._renderBody();
54
+ return this;
55
+ }
56
+ getColumns() { return [...this._columns]; }
57
+ // ═══════════════════════════════════════════════════════════
58
+ // STANDARD COLLECTION API
59
+ // ═══════════════════════════════════════════════════════════
60
+ addItem(item) {
61
+ this._items.push(item);
62
+ this._autoColumns(item);
63
+ this._renderBody();
64
+ this._dispatchChange();
65
+ return this;
66
+ }
67
+ addItems(items) {
68
+ for (const item of items) {
69
+ this._items.push(item);
70
+ this._autoColumns(item);
71
+ }
72
+ this._renderBody();
73
+ this._dispatchChange();
74
+ return this;
75
+ }
76
+ removeItem(key) {
77
+ const idx = typeof key === 'number' ? key : this._items.findIndex(i => i.id === key);
78
+ if (idx > -1) {
79
+ this._items.splice(idx, 1);
80
+ this._renderBody();
81
+ this._dispatchChange();
82
+ }
83
+ return this;
84
+ }
85
+ updateItem(key, updates) {
86
+ const idx = typeof key === 'number' ? key : this._items.findIndex(i => i.id === key);
87
+ if (idx > -1) {
88
+ this._items[idx] = { ...this._items[idx], ...updates };
89
+ this._renderBody();
90
+ this._dispatchChange();
91
+ }
92
+ return this;
93
+ }
94
+ clearItems() {
95
+ this._items = [];
96
+ this._value = null;
97
+ this._selectedIndex = -1;
98
+ this._renderBody();
99
+ this._dispatchChange();
100
+ return this;
101
+ }
102
+ getItems() { return [...this._items]; }
103
+ getCount() { return this._items.length; }
104
+ // ═══════════════════════════════════════════════════════════
105
+ // PAGESTATE INTEGRATION
106
+ // ═══════════════════════════════════════════════════════════
107
+ getValue() { return this._value; }
108
+ setValue(val) {
109
+ this._value = val;
110
+ return this;
111
+ }
112
+ getElement() { return this._element; }
113
+ onChange(fn) {
114
+ this._onChange = fn;
115
+ return this;
116
+ }
117
+ // ═══════════════════════════════════════════════════════════
118
+ // INTERNAL
119
+ // ═══════════════════════════════════════════════════════════
120
+ _autoColumns(item) {
121
+ if (this._columns.length > 0)
122
+ return;
123
+ const keys = Object.keys(item);
124
+ this._columns = keys.map(k => ({ key: k, label: k.charAt(0).toUpperCase() + k.slice(1) }));
125
+ this._renderHeader();
126
+ }
127
+ _renderHeader() {
128
+ this._thead.innerHTML = '';
129
+ const tr = document.createElement('tr');
130
+ for (const col of this._columns) {
131
+ const th = document.createElement('th');
132
+ th.textContent = col.label;
133
+ th.className = this.opts.headerClass || 'jux-table-th';
134
+ if (col.width)
135
+ th.style.width = col.width;
136
+ if (col.align)
137
+ th.style.textAlign = col.align;
138
+ tr.appendChild(th);
139
+ }
140
+ this._thead.appendChild(tr);
141
+ }
142
+ _renderBody() {
143
+ this._tbody.innerHTML = '';
144
+ this._items.forEach((item, idx) => {
145
+ const tr = document.createElement('tr');
146
+ tr.className = this.opts.rowClass || 'jux-table-tr';
147
+ if (this.opts.striped && idx % 2 === 1) {
148
+ tr.classList.add('jux-table-tr--striped');
149
+ }
150
+ if (this.opts.hoverable) {
151
+ tr.classList.add('jux-table-tr--hoverable');
152
+ }
153
+ for (const col of this._columns) {
154
+ const td = document.createElement('td');
155
+ td.className = this.opts.cellClass || 'jux-table-td';
156
+ if (col.align)
157
+ td.style.textAlign = col.align;
158
+ const raw = item[col.key];
159
+ td.textContent = col.render ? col.render(raw, item) : String(raw ?? '');
160
+ tr.appendChild(td);
161
+ }
162
+ if (this.opts.selectable) {
163
+ tr.style.cursor = 'pointer';
164
+ tr.addEventListener('click', () => {
165
+ this._value = item;
166
+ this._selectedIndex = idx;
167
+ if (this._onChange)
168
+ this._onChange(item);
169
+ this._element.dispatchEvent(new Event('change', { bubbles: false }));
170
+ this._highlightSelected();
171
+ });
172
+ }
173
+ this._tbody.appendChild(tr);
174
+ });
175
+ this._highlightSelected();
176
+ }
177
+ _highlightSelected() {
178
+ const rows = this._tbody.querySelectorAll('tr');
179
+ rows.forEach((row, idx) => {
180
+ if (idx === this._selectedIndex) {
181
+ row.classList.add('jux-table-tr--selected');
182
+ }
183
+ else {
184
+ row.classList.remove('jux-table-tr--selected');
185
+ }
186
+ });
187
+ }
188
+ _dispatchChange() {
189
+ this._element.setAttribute('data-count', String(this._items.length));
190
+ this._element.dispatchEvent(new Event('change', { bubbles: false }));
191
+ }
192
+ }
193
+ export function table(id, options = {}) {
194
+ const t = new Table(id, options);
195
+ pageState.__register(t);
196
+ return t;
197
+ }
198
+ export { Table };
199
+ export default table;
@@ -0,0 +1,52 @@
1
+ interface TabItem {
2
+ id?: string;
3
+ label: string;
4
+ content?: string;
5
+ icon?: string;
6
+ disabled?: boolean;
7
+ [key: string]: any;
8
+ }
9
+ interface TabsOptions {
10
+ items?: TabItem[];
11
+ defaultTab?: string;
12
+ class?: string;
13
+ style?: string;
14
+ target?: string;
15
+ tabListClass?: string;
16
+ tabClass?: string;
17
+ tabActiveClass?: string;
18
+ panelClass?: string;
19
+ }
20
+ declare class Tabs {
21
+ id: string;
22
+ opts: TabsOptions;
23
+ private _element;
24
+ private _tabList;
25
+ private _panelContainer;
26
+ private _items;
27
+ private _value;
28
+ private _onChange;
29
+ constructor(id: string, options?: TabsOptions);
30
+ addItem(item: TabItem): this;
31
+ addItems(items: TabItem[]): this;
32
+ removeItem(key: string | number): this;
33
+ updateItem(key: string | number, updates: Partial<TabItem>): this;
34
+ clearItems(): this;
35
+ getItems(): TabItem[];
36
+ getCount(): number;
37
+ /** Get the panel element for a tab so you can render jux components into it */
38
+ getPanel(tabId: string): HTMLElement | null;
39
+ getValue(): string | null;
40
+ setValue(val: string | null): this;
41
+ getElement(): HTMLElement;
42
+ onChange(fn: (value: string | null) => void): this;
43
+ private _renderTab;
44
+ private _renderPanel;
45
+ private _renderAll;
46
+ private _updateActiveState;
47
+ private _dispatchChange;
48
+ }
49
+ export declare function tabs(id: string, options?: TabsOptions): Tabs;
50
+ export { Tabs, TabItem, TabsOptions };
51
+ export default tabs;
52
+ //# sourceMappingURL=tabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../lib/components/tabs.ts"],"names":[],"mappings":"AAGA,UAAU,OAAO;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,UAAU,WAAW;IACjB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,cAAM,IAAI;IACN,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAAiD;gBAEtD,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB;IAgDjD,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAU5B,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI;IAchC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAetC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;IAWjE,UAAU,IAAI,IAAI;IASlB,QAAQ,IAAI,OAAO,EAAE;IACrB,QAAQ,IAAI,MAAM;IAMlB,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAQ3C,QAAQ,IAAI,MAAM,GAAG,IAAI;IAEzB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAMlC,UAAU,IAAI,WAAW;IAEzB,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;IASlD,OAAO,CAAC,UAAU;IAuBlB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,kBAAkB;IAsB1B,OAAO,CAAC,eAAe;CAI1B;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,IAAI,CAIhE;AAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AACtC,eAAe,IAAI,CAAC"}
@@ -0,0 +1,206 @@
1
+ import generateId from '../utils/idgen.js';
2
+ import { pageState } from '../state/pageState.js';
3
+ class Tabs {
4
+ constructor(id, options = {}) {
5
+ this._items = [];
6
+ this._value = null;
7
+ this._onChange = null;
8
+ this.id = id || generateId();
9
+ this.opts = {
10
+ tabListClass: 'jux-tabs-list',
11
+ tabClass: 'jux-tab',
12
+ tabActiveClass: 'jux-tab--active',
13
+ panelClass: 'jux-tab-panel',
14
+ ...options
15
+ };
16
+ this._element = document.createElement('div');
17
+ this._element.id = this.id;
18
+ this._element.setAttribute('data-jux-tabs', '');
19
+ if (this.opts.class)
20
+ this._element.className = this.opts.class;
21
+ if (this.opts.style)
22
+ this._element.setAttribute('style', this.opts.style);
23
+ // Tab list (the clickable triggers)
24
+ this._tabList = document.createElement('div');
25
+ this._tabList.className = this.opts.tabListClass;
26
+ this._tabList.setAttribute('role', 'tablist');
27
+ this._element.appendChild(this._tabList);
28
+ // Panel container (the content areas)
29
+ this._panelContainer = document.createElement('div');
30
+ this._panelContainer.className = 'jux-tabs-panels';
31
+ this._element.appendChild(this._panelContainer);
32
+ const resolvedTarget = this.opts.target;
33
+ const container = resolvedTarget
34
+ ? document.getElementById(resolvedTarget) || document.querySelector(resolvedTarget)
35
+ : document.getElementById('app');
36
+ container?.appendChild(this._element);
37
+ if (this.opts.items) {
38
+ this.addItems(this.opts.items);
39
+ }
40
+ if (this.opts.defaultTab) {
41
+ this.setValue(this.opts.defaultTab);
42
+ }
43
+ else if (this._items.length > 0) {
44
+ this.setValue(this._items[0].id);
45
+ }
46
+ }
47
+ // ═══════════════════════════════════════════════════════════
48
+ // STANDARD COLLECTION API
49
+ // ═══════════════════════════════════════════════════════════
50
+ addItem(item) {
51
+ const tabItem = { ...item, id: item.id || `${this.id}-tab-${this._items.length}` };
52
+ this._items.push(tabItem);
53
+ this._renderTab(tabItem);
54
+ this._renderPanel(tabItem);
55
+ if (this._items.length === 1)
56
+ this.setValue(tabItem.id);
57
+ this._dispatchChange();
58
+ return this;
59
+ }
60
+ addItems(items) {
61
+ for (const item of items) {
62
+ const tabItem = { ...item, id: item.id || `${this.id}-tab-${this._items.length}` };
63
+ this._items.push(tabItem);
64
+ this._renderTab(tabItem);
65
+ this._renderPanel(tabItem);
66
+ }
67
+ if (this._items.length > 0 && !this._value) {
68
+ this.setValue(this._items[0].id);
69
+ }
70
+ this._dispatchChange();
71
+ return this;
72
+ }
73
+ removeItem(key) {
74
+ const idx = typeof key === 'number' ? key : this._items.findIndex(i => i.id === key);
75
+ if (idx > -1) {
76
+ const removed = this._items.splice(idx, 1)[0];
77
+ this._renderAll();
78
+ if (this._value === removed.id && this._items.length > 0) {
79
+ this.setValue(this._items[0].id);
80
+ }
81
+ else if (this._items.length === 0) {
82
+ this._value = null;
83
+ }
84
+ this._dispatchChange();
85
+ }
86
+ return this;
87
+ }
88
+ updateItem(key, updates) {
89
+ const idx = typeof key === 'number' ? key : this._items.findIndex(i => i.id === key);
90
+ if (idx > -1) {
91
+ this._items[idx] = { ...this._items[idx], ...updates };
92
+ this._renderAll();
93
+ this._updateActiveState();
94
+ this._dispatchChange();
95
+ }
96
+ return this;
97
+ }
98
+ clearItems() {
99
+ this._items = [];
100
+ this._value = null;
101
+ this._tabList.innerHTML = '';
102
+ this._panelContainer.innerHTML = '';
103
+ this._dispatchChange();
104
+ return this;
105
+ }
106
+ getItems() { return [...this._items]; }
107
+ getCount() { return this._items.length; }
108
+ // ═══════════════════════════════════════════════════════════
109
+ // PANEL ACCESS (for rendering content into tab panels)
110
+ // ═══════════════════════════════════════════════════════════
111
+ /** Get the panel element for a tab so you can render jux components into it */
112
+ getPanel(tabId) {
113
+ return this._panelContainer.querySelector(`[data-tab-panel="${tabId}"]`);
114
+ }
115
+ // ═══════════════════════════════════════════════════════════
116
+ // PAGESTATE INTEGRATION
117
+ // ═══════════════════════════════════════════════════════════
118
+ getValue() { return this._value; }
119
+ setValue(val) {
120
+ this._value = val;
121
+ this._updateActiveState();
122
+ return this;
123
+ }
124
+ getElement() { return this._element; }
125
+ onChange(fn) {
126
+ this._onChange = fn;
127
+ return this;
128
+ }
129
+ // ═══════════════════════════════════════════════════════════
130
+ // INTERNAL
131
+ // ═══════════════════════════════════════════════════════════
132
+ _renderTab(item) {
133
+ const btn = document.createElement('button');
134
+ btn.id = `${item.id}-trigger`;
135
+ btn.className = this.opts.tabClass;
136
+ btn.setAttribute('role', 'tab');
137
+ btn.setAttribute('data-tab-id', item.id);
138
+ btn.textContent = item.label;
139
+ if (item.disabled) {
140
+ btn.disabled = true;
141
+ btn.setAttribute('data-disabled', 'true');
142
+ }
143
+ btn.addEventListener('click', () => {
144
+ if (item.disabled)
145
+ return;
146
+ this._value = item.id;
147
+ this._updateActiveState();
148
+ if (this._onChange)
149
+ this._onChange(this._value);
150
+ this._element.dispatchEvent(new Event('change', { bubbles: false }));
151
+ });
152
+ this._tabList.appendChild(btn);
153
+ }
154
+ _renderPanel(item) {
155
+ const panel = document.createElement('div');
156
+ panel.id = `${item.id}-panel`;
157
+ panel.className = this.opts.panelClass;
158
+ panel.setAttribute('role', 'tabpanel');
159
+ panel.setAttribute('data-tab-panel', item.id);
160
+ if (item.content)
161
+ panel.innerHTML = item.content;
162
+ panel.style.display = 'none';
163
+ this._panelContainer.appendChild(panel);
164
+ }
165
+ _renderAll() {
166
+ this._tabList.innerHTML = '';
167
+ this._panelContainer.innerHTML = '';
168
+ for (const item of this._items) {
169
+ this._renderTab(item);
170
+ this._renderPanel(item);
171
+ }
172
+ this._updateActiveState();
173
+ }
174
+ _updateActiveState() {
175
+ // Tabs
176
+ const tabs = this._tabList.querySelectorAll('[role="tab"]');
177
+ tabs.forEach(tab => {
178
+ const tabId = tab.getAttribute('data-tab-id');
179
+ if (tabId === this._value) {
180
+ tab.classList.add(this.opts.tabActiveClass);
181
+ tab.setAttribute('aria-selected', 'true');
182
+ }
183
+ else {
184
+ tab.classList.remove(this.opts.tabActiveClass);
185
+ tab.setAttribute('aria-selected', 'false');
186
+ }
187
+ });
188
+ // Panels
189
+ const panels = this._panelContainer.querySelectorAll('[role="tabpanel"]');
190
+ panels.forEach(panel => {
191
+ const panelId = panel.getAttribute('data-tab-panel');
192
+ panel.style.display = panelId === this._value ? '' : 'none';
193
+ });
194
+ }
195
+ _dispatchChange() {
196
+ this._element.setAttribute('data-count', String(this._items.length));
197
+ this._element.dispatchEvent(new Event('change', { bubbles: false }));
198
+ }
199
+ }
200
+ export function tabs(id, options = {}) {
201
+ const t = new Tabs(id, options);
202
+ pageState.__register(t);
203
+ return t;
204
+ }
205
+ export { Tabs };
206
+ export default tabs;