juxscript 1.1.112 → 1.1.115

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,216 @@
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+ import { renderIcon } from './icons.js';
3
+ import { formatIdAsLabel } from '../utils/formatId.js';
4
+ // Event definitions
5
+ const TRIGGER_EVENTS = [];
6
+ const CALLBACK_EVENTS = ['click'];
7
+ export class Link extends BaseComponent {
8
+ constructor(id, options = {}) {
9
+ super(id, {
10
+ visible: true,
11
+ disabled: options.disabled ?? false,
12
+ loading: false,
13
+ class: options.class ?? '',
14
+ style: options.style ?? '',
15
+ attributes: {},
16
+ href: options.href ?? `/${id}`,
17
+ text: options.text ?? formatIdAsLabel(id),
18
+ icon: options.icon ?? '',
19
+ iconPosition: options.iconPosition ?? 'left',
20
+ target: options.target ?? '_self',
21
+ external: options.external ?? false,
22
+ active: options.active ?? false
23
+ });
24
+ }
25
+ getTriggerEvents() {
26
+ return TRIGGER_EVENTS;
27
+ }
28
+ getCallbackEvents() {
29
+ return CALLBACK_EVENTS;
30
+ }
31
+ /* ═════════════════════════════════════════════════════════════════
32
+ * FLUENT API
33
+ * ═════════════════════════════════════════════════════════════════ */
34
+ href(value) {
35
+ this.state.href = value;
36
+ if (this.container) {
37
+ const link = this.container.querySelector(`#${this._id}`);
38
+ if (link)
39
+ link.href = value;
40
+ }
41
+ return this;
42
+ }
43
+ text(value) {
44
+ this.state.text = value;
45
+ if (this.container) {
46
+ const link = this.container.querySelector(`#${this._id}`);
47
+ if (link) {
48
+ const textNode = link.querySelector('.jux-link-text');
49
+ if (textNode)
50
+ textNode.textContent = value;
51
+ }
52
+ }
53
+ return this;
54
+ }
55
+ icon(value) {
56
+ this.state.icon = value;
57
+ return this;
58
+ }
59
+ iconPosition(value) {
60
+ this.state.iconPosition = value;
61
+ return this;
62
+ }
63
+ target(value) {
64
+ this.state.target = value;
65
+ if (this.container) {
66
+ const link = this.container.querySelector(`#${this._id}`);
67
+ if (link)
68
+ link.target = value;
69
+ }
70
+ return this;
71
+ }
72
+ external(value = true) {
73
+ this.state.external = value;
74
+ if (value) {
75
+ this.state.target = '_blank';
76
+ }
77
+ return this;
78
+ }
79
+ active(value = true) {
80
+ this.state.active = value;
81
+ if (this.container) {
82
+ const link = this.container.querySelector(`#${this._id}`);
83
+ if (link)
84
+ link.classList.toggle('jux-link-active', value);
85
+ }
86
+ return this;
87
+ }
88
+ /* ═════════════════════════════════════════════════════════════════
89
+ * RENDER
90
+ * ═════════════════════════════════════════════════════════════════ */
91
+ render(targetId) {
92
+ const container = this._setupContainer(targetId);
93
+ const { href, text, icon, iconPosition, target, external, active, disabled, style, class: className } = this.state;
94
+ const link = document.createElement('a');
95
+ link.id = this._id;
96
+ link.className = 'jux-link';
97
+ if (active)
98
+ link.classList.add('jux-link-active');
99
+ if (disabled)
100
+ link.classList.add('jux-link-disabled');
101
+ if (className)
102
+ link.className += ` ${className}`;
103
+ if (style)
104
+ link.setAttribute('style', style);
105
+ link.href = href;
106
+ link.target = target;
107
+ // Add rel for external links
108
+ if (external || target === '_blank') {
109
+ link.rel = 'noopener noreferrer';
110
+ }
111
+ // Mark as external link for router to ignore
112
+ if (external) {
113
+ link.dataset.router = 'false';
114
+ }
115
+ // Icon (left position)
116
+ if (icon && iconPosition === 'left') {
117
+ const iconEl = document.createElement('span');
118
+ iconEl.className = 'jux-link-icon jux-link-icon-left';
119
+ iconEl.appendChild(renderIcon(icon));
120
+ link.appendChild(iconEl);
121
+ }
122
+ // Text
123
+ const textEl = document.createElement('span');
124
+ textEl.className = 'jux-link-text';
125
+ textEl.textContent = text;
126
+ link.appendChild(textEl);
127
+ // Icon (right position)
128
+ if (icon && iconPosition === 'right') {
129
+ const iconEl = document.createElement('span');
130
+ iconEl.className = 'jux-link-icon jux-link-icon-right';
131
+ iconEl.appendChild(renderIcon(icon));
132
+ link.appendChild(iconEl);
133
+ }
134
+ // Wire events
135
+ this._wireStandardEvents(link);
136
+ // Fire click callback
137
+ link.addEventListener('click', (e) => {
138
+ if (disabled) {
139
+ e.preventDefault();
140
+ return;
141
+ }
142
+ this._triggerCallback('click', href);
143
+ });
144
+ // Sync href changes
145
+ const hrefSync = this._syncBindings.find(b => b.property === 'href');
146
+ if (hrefSync) {
147
+ const transform = hrefSync.toComponent || ((v) => String(v));
148
+ hrefSync.stateObj.subscribe((val) => {
149
+ this.href(transform(val));
150
+ });
151
+ }
152
+ // Sync text changes
153
+ const textSync = this._syncBindings.find(b => b.property === 'text');
154
+ if (textSync) {
155
+ const transform = textSync.toComponent || ((v) => String(v));
156
+ textSync.stateObj.subscribe((val) => {
157
+ this.text(transform(val));
158
+ });
159
+ }
160
+ container.appendChild(link);
161
+ requestAnimationFrame(() => {
162
+ if (window.lucide) {
163
+ window.lucide.createIcons();
164
+ }
165
+ });
166
+ return this;
167
+ }
168
+ update(prop, value) {
169
+ if (!this.container)
170
+ return;
171
+ const link = this.container.querySelector(`#${this._id}`);
172
+ if (!link)
173
+ return;
174
+ switch (prop) {
175
+ case 'href':
176
+ link.href = value;
177
+ break;
178
+ case 'text':
179
+ const textNode = link.querySelector('.jux-link-text');
180
+ if (textNode)
181
+ textNode.textContent = value;
182
+ break;
183
+ case 'active':
184
+ link.classList.toggle('jux-link-active', value);
185
+ break;
186
+ case 'disabled':
187
+ link.classList.toggle('jux-link-disabled', value);
188
+ break;
189
+ case 'target':
190
+ link.target = value;
191
+ break;
192
+ default:
193
+ super.update(prop, value);
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ export function link(id, options = {}) {
199
+ return new Link(id, options);
200
+ }
201
+ // Convenience helpers
202
+ export function navLink(id, text) {
203
+ return new Link(id, {
204
+ href: `/${id}`,
205
+ text: text || formatIdAsLabel(id)
206
+ });
207
+ }
208
+ export function externalLink(id, href, text) {
209
+ return new Link(id, {
210
+ href,
211
+ text: text || formatIdAsLabel(id),
212
+ external: true,
213
+ icon: 'external-link',
214
+ iconPosition: 'right'
215
+ });
216
+ }
@@ -0,0 +1,268 @@
1
+ import { BaseComponent, BaseState } from './base/BaseComponent.js';
2
+ import { renderIcon } from './icons.js';
3
+ import { formatIdAsLabel } from '../utils/formatId.js';
4
+
5
+ // Event definitions
6
+ const TRIGGER_EVENTS = [] as const;
7
+ const CALLBACK_EVENTS = ['click'] as const;
8
+
9
+ export interface LinkOptions {
10
+ href?: string;
11
+ text?: string;
12
+ icon?: string;
13
+ iconPosition?: 'left' | 'right';
14
+ target?: '_self' | '_blank' | '_parent' | '_top';
15
+ external?: boolean;
16
+ active?: boolean;
17
+ disabled?: boolean;
18
+ style?: string;
19
+ class?: string;
20
+ }
21
+
22
+ interface LinkState extends BaseState {
23
+ href: string;
24
+ text: string;
25
+ icon: string;
26
+ iconPosition: 'left' | 'right';
27
+ target: string;
28
+ external: boolean;
29
+ active: boolean;
30
+ }
31
+
32
+ export class Link extends BaseComponent<LinkState> {
33
+ constructor(id: string, options: LinkOptions = {}) {
34
+ super(id, {
35
+ visible: true,
36
+ disabled: options.disabled ?? false,
37
+ loading: false,
38
+ class: options.class ?? '',
39
+ style: options.style ?? '',
40
+ attributes: {},
41
+ href: options.href ?? `/${id}`,
42
+ text: options.text ?? formatIdAsLabel(id),
43
+ icon: options.icon ?? '',
44
+ iconPosition: options.iconPosition ?? 'left',
45
+ target: options.target ?? '_self',
46
+ external: options.external ?? false,
47
+ active: options.active ?? false
48
+ });
49
+ }
50
+
51
+ protected getTriggerEvents(): readonly string[] {
52
+ return TRIGGER_EVENTS;
53
+ }
54
+
55
+ protected getCallbackEvents(): readonly string[] {
56
+ return CALLBACK_EVENTS;
57
+ }
58
+
59
+ /* ═════════════════════════════════════════════════════════════════
60
+ * FLUENT API
61
+ * ═════════════════════════════════════════════════════════════════ */
62
+
63
+ href(value: string): this {
64
+ this.state.href = value;
65
+ if (this.container) {
66
+ const link = this.container.querySelector(`#${this._id}`) as HTMLAnchorElement;
67
+ if (link) link.href = value;
68
+ }
69
+ return this;
70
+ }
71
+
72
+ text(value: string): this {
73
+ this.state.text = value;
74
+ if (this.container) {
75
+ const link = this.container.querySelector(`#${this._id}`) as HTMLAnchorElement;
76
+ if (link) {
77
+ const textNode = link.querySelector('.jux-link-text');
78
+ if (textNode) textNode.textContent = value;
79
+ }
80
+ }
81
+ return this;
82
+ }
83
+
84
+ icon(value: string): this {
85
+ this.state.icon = value;
86
+ return this;
87
+ }
88
+
89
+ iconPosition(value: 'left' | 'right'): this {
90
+ this.state.iconPosition = value;
91
+ return this;
92
+ }
93
+
94
+ target(value: '_self' | '_blank' | '_parent' | '_top'): this {
95
+ this.state.target = value;
96
+ if (this.container) {
97
+ const link = this.container.querySelector(`#${this._id}`) as HTMLAnchorElement;
98
+ if (link) link.target = value;
99
+ }
100
+ return this;
101
+ }
102
+
103
+ external(value: boolean = true): this {
104
+ this.state.external = value;
105
+ if (value) {
106
+ this.state.target = '_blank';
107
+ }
108
+ return this;
109
+ }
110
+
111
+ active(value: boolean = true): this {
112
+ this.state.active = value;
113
+ if (this.container) {
114
+ const link = this.container.querySelector(`#${this._id}`) as HTMLAnchorElement;
115
+ if (link) link.classList.toggle('jux-link-active', value);
116
+ }
117
+ return this;
118
+ }
119
+
120
+ /* ═════════════════════════════════════════════════════════════════
121
+ * RENDER
122
+ * ═════════════════════════════════════════════════════════════════ */
123
+
124
+ render(targetId?: string | HTMLElement | BaseComponent<any>): this {
125
+ const container = this._setupContainer(targetId);
126
+
127
+ const { href, text, icon, iconPosition, target, external, active, disabled, style, class: className } = this.state;
128
+
129
+ const link = document.createElement('a');
130
+ link.id = this._id;
131
+ link.className = 'jux-link';
132
+ if (active) link.classList.add('jux-link-active');
133
+ if (disabled) link.classList.add('jux-link-disabled');
134
+ if (className) link.className += ` ${className}`;
135
+ if (style) link.setAttribute('style', style);
136
+
137
+ link.href = href;
138
+ link.target = target;
139
+
140
+ // Add rel for external links
141
+ if (external || target === '_blank') {
142
+ link.rel = 'noopener noreferrer';
143
+ }
144
+
145
+ // Mark as external link for router to ignore
146
+ if (external) {
147
+ link.dataset.router = 'false';
148
+ }
149
+
150
+ // Icon (left position)
151
+ if (icon && iconPosition === 'left') {
152
+ const iconEl = document.createElement('span');
153
+ iconEl.className = 'jux-link-icon jux-link-icon-left';
154
+ iconEl.appendChild(renderIcon(icon));
155
+ link.appendChild(iconEl);
156
+ }
157
+
158
+ // Text
159
+ const textEl = document.createElement('span');
160
+ textEl.className = 'jux-link-text';
161
+ textEl.textContent = text;
162
+ link.appendChild(textEl);
163
+
164
+ // Icon (right position)
165
+ if (icon && iconPosition === 'right') {
166
+ const iconEl = document.createElement('span');
167
+ iconEl.className = 'jux-link-icon jux-link-icon-right';
168
+ iconEl.appendChild(renderIcon(icon));
169
+ link.appendChild(iconEl);
170
+ }
171
+
172
+ // Wire events
173
+ this._wireStandardEvents(link);
174
+
175
+ // Fire click callback
176
+ link.addEventListener('click', (e) => {
177
+ if (disabled) {
178
+ e.preventDefault();
179
+ return;
180
+ }
181
+ this._triggerCallback('click', href);
182
+ });
183
+
184
+ // Sync href changes
185
+ const hrefSync = this._syncBindings.find(b => b.property === 'href');
186
+ if (hrefSync) {
187
+ const transform = hrefSync.toComponent || ((v: any) => String(v));
188
+ hrefSync.stateObj.subscribe((val: any) => {
189
+ this.href(transform(val));
190
+ });
191
+ }
192
+
193
+ // Sync text changes
194
+ const textSync = this._syncBindings.find(b => b.property === 'text');
195
+ if (textSync) {
196
+ const transform = textSync.toComponent || ((v: any) => String(v));
197
+ textSync.stateObj.subscribe((val: any) => {
198
+ this.text(transform(val));
199
+ });
200
+ }
201
+
202
+ container.appendChild(link);
203
+
204
+ requestAnimationFrame(() => {
205
+ if ((window as any).lucide) {
206
+ (window as any).lucide.createIcons();
207
+ }
208
+ });
209
+
210
+ return this;
211
+ }
212
+
213
+ update(prop: string, value: any): void {
214
+ if (!this.container) return;
215
+
216
+ const link = this.container.querySelector(`#${this._id}`) as HTMLAnchorElement;
217
+ if (!link) return;
218
+
219
+ switch (prop) {
220
+ case 'href':
221
+ link.href = value;
222
+ break;
223
+
224
+ case 'text':
225
+ const textNode = link.querySelector('.jux-link-text');
226
+ if (textNode) textNode.textContent = value;
227
+ break;
228
+
229
+ case 'active':
230
+ link.classList.toggle('jux-link-active', value);
231
+ break;
232
+
233
+ case 'disabled':
234
+ link.classList.toggle('jux-link-disabled', value);
235
+ break;
236
+
237
+ case 'target':
238
+ link.target = value;
239
+ break;
240
+
241
+ default:
242
+ super.update(prop, value);
243
+ break;
244
+ }
245
+ }
246
+ }
247
+
248
+ export function link(id: string, options: LinkOptions = {}): Link {
249
+ return new Link(id, options);
250
+ }
251
+
252
+ // Convenience helpers
253
+ export function navLink(id: string, text?: string): Link {
254
+ return new Link(id, {
255
+ href: `/${id}`,
256
+ text: text || formatIdAsLabel(id)
257
+ });
258
+ }
259
+
260
+ export function externalLink(id: string, href: string, text?: string): Link {
261
+ return new Link(id, {
262
+ href,
263
+ text: text || formatIdAsLabel(id),
264
+ external: true,
265
+ icon: 'external-link',
266
+ iconPosition: 'right'
267
+ });
268
+ }