@nuraly/lumenui 0.2.2 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/cdn.js +1 -0
  2. package/dist/nuralyui.bundle.js +2467 -1857
  3. package/dist/nuralyui.bundle.js.gz +0 -0
  4. package/dist/src/components/canvas/bundle.js +569 -204
  5. package/dist/src/components/canvas/bundle.js.gz +0 -0
  6. package/dist/src/components/canvas/canvas.constants.d.ts +1 -1
  7. package/dist/src/components/canvas/canvas.constants.js +1 -1
  8. package/dist/src/components/canvas/workflow-canvas.component.d.ts +7 -0
  9. package/dist/src/components/canvas/workflow-canvas.component.js +46 -2
  10. package/dist/src/components/canvas/workflow-canvas.types.d.ts +8 -1
  11. package/dist/src/components/canvas/workflow-canvas.types.js +157 -0
  12. package/dist/src/components/chat-panel/bundle.js +216 -0
  13. package/dist/src/components/chat-panel/bundle.js.gz +0 -0
  14. package/dist/src/components/chat-panel/chat-panel.component.d.ts +85 -0
  15. package/dist/src/components/chat-panel/chat-panel.component.js +510 -0
  16. package/dist/src/components/chat-panel/chat-panel.style.d.ts +2 -0
  17. package/dist/src/components/chat-panel/chat-panel.style.js +127 -0
  18. package/dist/src/components/chat-panel/chat-panel.types.d.ts +54 -0
  19. package/dist/src/components/chat-panel/chat-panel.types.js +2 -0
  20. package/dist/src/components/chat-panel/index.d.ts +3 -0
  21. package/dist/src/components/chat-panel/index.js +2 -0
  22. package/dist/src/components/chatbot/bundle.js +1 -1
  23. package/dist/src/components/chatbot/bundle.js.gz +0 -0
  24. package/dist/src/components/chatbot/providers/workflow-socket-provider.js +1 -1
  25. package/dist/src/components/presence/bundle.js +69 -42
  26. package/dist/src/components/presence/bundle.js.gz +0 -0
  27. package/dist/src/components/presence/presence-chat.component.d.ts +2 -0
  28. package/dist/src/components/presence/presence-chat.component.js +12 -2
  29. package/dist/src/components/presence/presence.component.d.ts +30 -6
  30. package/dist/src/components/presence/presence.component.js +277 -30
  31. package/dist/src/components/presence/presence.types.d.ts +2 -0
  32. package/dist/src/components/toast/bundle.js +11 -9
  33. package/dist/src/components/toast/bundle.js.gz +0 -0
  34. package/dist/src/components/toast/toast.component.d.ts +8 -0
  35. package/dist/src/components/toast/toast.component.js +17 -7
  36. package/dist/src/components/video/bundle.js +13 -12
  37. package/dist/src/components/video/bundle.js.gz +0 -0
  38. package/dist/src/components/video/video.component.d.ts +15 -1
  39. package/dist/src/components/video/video.component.js +131 -3
  40. package/package.json +5 -10
  41. package/packages/common/dist/VERSIONS.md +1 -1
@@ -18,17 +18,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
18
18
  step((generator = generator.apply(thisArg, _arguments || [])).next());
19
19
  });
20
20
  };
21
- import { LitElement, html, nothing } from 'lit';
21
+ import { LitElement, html, nothing, css } from 'lit';
22
22
  import { customElement, property, state } from 'lit/decorators.js';
23
23
  import { NuralyUIBaseMixin } from '@nuralyui/common/mixins';
24
24
  import { styles } from './presence.style.js';
25
25
  import './presence-avatars.component.js';
26
26
  import './presence-chat.component.js';
27
+ const GROUP_CHAT_KEY = '__group__';
27
28
  /**
28
29
  * Top-level presence orchestrator.
29
30
  *
30
- * Owns the socket connection, viewer list, and chat panel manager.
31
- * Renders `nr-presence-avatars` + one `nr-presence-chat` per open chat.
31
+ * Owns the resource socket for viewer tracking, and uses the global messages
32
+ * socket (`.globalSocket`) for real-time chat.
33
+ *
34
+ * Two chat modes:
35
+ * - **Group chat**: One conversation per resource. Opened via the chat button.
36
+ * - **DM**: Private 1-on-1 conversation. Opened by clicking a viewer's avatar.
32
37
  *
33
38
  * @example
34
39
  * ```html
@@ -36,9 +41,11 @@ import './presence-chat.component.js';
36
41
  * namespace="/nk/apps/workflows/socket"
37
42
  * socket-path="/__nk_socketio/"
38
43
  * resource-id="workflow-abc123"
44
+ * resource-type="workflow"
45
+ * resource-name="My Workflow"
39
46
  * user-id="user-xyz"
40
47
  * .currentUser=${{ displayName, initials, color, avatarUrl }}
41
- * .extraUsers=${[...mockUsers]}
48
+ * .globalSocket=${globalSocket}
42
49
  * ></nr-presence>
43
50
  * ```
44
51
  *
@@ -47,31 +54,43 @@ import './presence-chat.component.js';
47
54
  let NrPresenceElement = class NrPresenceElement extends NuralyUIBaseMixin(LitElement) {
48
55
  constructor() {
49
56
  super(...arguments);
50
- /** Socket.io namespace */
57
+ /** Socket.io namespace for presence tracking */
51
58
  this.namespace = '';
52
59
  /** Socket.io path */
53
60
  this.socketPath = '/__nk_socketio/';
54
61
  /** The resource being viewed (workflow id, doc id, etc.) */
55
62
  this.resourceId = '';
63
+ /** Resource type for group conversation (e.g. 'workflow', 'whiteboard') */
64
+ this.resourceType = '';
65
+ /** Resource name (used as group conversation name) */
66
+ this.resourceName = '';
56
67
  /** Current user's id */
57
68
  this.userId = '';
58
69
  /** Current user object (shown first in avatar strip) */
59
70
  this.currentUser = null;
60
71
  /** Additional users to show (e.g. mocks for demo) */
61
72
  this.extraUsers = [];
73
+ /** Global messages socket (from layout's /nk/messages connection) for real-time chat */
74
+ this.globalSocket = null;
62
75
  this._viewers = [];
76
+ this._hasUnreadGroup = false;
63
77
  this._socket = null;
78
+ this._connecting = false;
64
79
  this._chats = new Map();
65
80
  this._chatZ = 100;
66
81
  this._drag = null;
67
82
  this._boundDragMove = (e) => this._onDragMove(e);
68
83
  this._boundDragEnd = () => this._onDragEnd();
69
84
  this._boundEscape = (e) => this._onEscape(e);
85
+ this._boundOnMessage = (data) => this._onSocketMessage(data);
86
+ // ── Group chat ──
87
+ this._groupConversationId = null;
70
88
  }
71
89
  connectedCallback() {
72
90
  super.connectedCallback();
73
91
  document.addEventListener('keydown', this._boundEscape);
74
92
  this._connect();
93
+ this._attachGlobalSocket();
75
94
  }
76
95
  disconnectedCallback() {
77
96
  var _a;
@@ -81,11 +100,66 @@ let NrPresenceElement = class NrPresenceElement extends NuralyUIBaseMixin(LitEle
81
100
  document.removeEventListener('mouseup', this._boundDragEnd);
82
101
  (_a = this._socket) === null || _a === void 0 ? void 0 : _a.disconnect();
83
102
  this._socket = null;
103
+ this._detachGlobalSocket();
104
+ }
105
+ updated(changed) {
106
+ if (changed.has('globalSocket')) {
107
+ this._detachGlobalSocket(changed.get('globalSocket'));
108
+ this._attachGlobalSocket();
109
+ }
110
+ // Resource socket may not have connected during connectedCallback if userId/
111
+ // resourceId/namespace were still empty (common during hydration). Retry when
112
+ // any of them becomes available.
113
+ if (!this._socket && (changed.has('userId') || changed.has('resourceId') || changed.has('namespace'))) {
114
+ if (this.userId && this.resourceId && this.namespace) {
115
+ this._connect();
116
+ }
117
+ }
84
118
  }
119
+ _attachGlobalSocket() {
120
+ if (this.globalSocket) {
121
+ this.globalSocket.on('nk:data', this._boundOnMessage);
122
+ }
123
+ }
124
+ _detachGlobalSocket(old) {
125
+ const sock = old || this.globalSocket;
126
+ if (sock) {
127
+ sock.off('nk:data', this._boundOnMessage);
128
+ }
129
+ }
130
+ /** Handle incoming messages from the global /nk/messages socket */
131
+ _onSocketMessage(data) {
132
+ if (data.event !== 'message:new')
133
+ return;
134
+ const msg = data.data;
135
+ if (!(msg === null || msg === void 0 ? void 0 : msg.conversationId) || msg.senderId === this.userId)
136
+ return;
137
+ for (const [, chat] of this._chats) {
138
+ if (chat.conversationId === msg.conversationId) {
139
+ chat.messages = [...chat.messages, {
140
+ id: String(msg.id),
141
+ senderId: msg.senderId,
142
+ text: msg.content,
143
+ timestamp: new Date(msg.createdAt).getTime(),
144
+ me: false,
145
+ }];
146
+ this.requestUpdate();
147
+ return;
148
+ }
149
+ }
150
+ // If message is for our group conversation but panel is closed, show unread dot
151
+ if (this._groupConversationId && msg.conversationId === this._groupConversationId) {
152
+ this._hasUnreadGroup = true;
153
+ }
154
+ }
155
+ // ── Resource presence socket ──
85
156
  _connect() {
86
157
  return __awaiter(this, void 0, void 0, function* () {
158
+ if (this._socket || this._connecting)
159
+ return;
87
160
  if (!this.userId || !this.resourceId || !this.namespace)
88
161
  return;
162
+ this._connecting = true;
89
163
  try {
90
164
  const { io } = yield import('socket.io-client');
91
165
  this._socket = io(this.namespace, {
@@ -108,24 +182,172 @@ let NrPresenceElement = class NrPresenceElement extends NuralyUIBaseMixin(LitEle
108
182
  catch (e) {
109
183
  console.error('[nr-presence] Connection failed:', e);
110
184
  }
185
+ finally {
186
+ this._connecting = false;
187
+ }
111
188
  });
112
189
  }
113
- _openChat(user) {
114
- const key = user.userId || user.displayName;
115
- if (this._chats.has(key)) {
116
- this._chats.get(key).z = ++this._chatZ;
117
- }
118
- else {
119
- const offset = this._chats.size * 24;
120
- const ix = window.innerWidth - 324 - offset;
121
- const iy = 56 + offset;
190
+ _openGroupChat() {
191
+ return __awaiter(this, void 0, void 0, function* () {
192
+ var _a;
193
+ if (this._chats.has(GROUP_CHAT_KEY)) {
194
+ const c = this._chats.get(GROUP_CHAT_KEY);
195
+ c.minimized = false;
196
+ c.z = ++this._chatZ;
197
+ this._hasUnreadGroup = false;
198
+ this.requestUpdate();
199
+ return;
200
+ }
201
+ const ix = window.innerWidth - 324;
202
+ const iy = 56;
203
+ this._chats.set(GROUP_CHAT_KEY, {
204
+ user: { displayName: this.resourceName || `${this.resourceType} chat`, color: '#7c3aed', initials: '#' },
205
+ conversationId: null, loading: true,
206
+ x: ix, y: iy, savedX: ix, savedY: iy,
207
+ z: ++this._chatZ, minimized: false, messages: [], draftText: '',
208
+ });
209
+ this._hasUnreadGroup = false;
210
+ this.requestUpdate();
211
+ try {
212
+ const res = yield fetch('/api/conversations/resource', {
213
+ method: 'POST',
214
+ headers: { 'Content-Type': 'application/json' },
215
+ body: JSON.stringify({
216
+ resourceType: this.resourceType,
217
+ resourceId: this.resourceId,
218
+ resourceName: this.resourceName,
219
+ }),
220
+ });
221
+ const data = yield res.json();
222
+ const chat = this._chats.get(GROUP_CHAT_KEY);
223
+ if (!chat || !data.conversationId) {
224
+ if (chat)
225
+ chat.loading = false;
226
+ this.requestUpdate();
227
+ return;
228
+ }
229
+ chat.conversationId = data.conversationId;
230
+ this._groupConversationId = data.conversationId;
231
+ // Join the conversation room on the global socket
232
+ if ((_a = this.globalSocket) === null || _a === void 0 ? void 0 : _a.connected) {
233
+ this.globalSocket.emit('nk:conversation:join', { conversationId: data.conversationId });
234
+ }
235
+ // Load existing messages
236
+ yield this._loadMessages(chat);
237
+ }
238
+ catch (e) {
239
+ console.error('[nr-presence] Failed to open group chat:', e);
240
+ const chat = this._chats.get(GROUP_CHAT_KEY);
241
+ if (chat)
242
+ chat.loading = false;
243
+ }
244
+ this.requestUpdate();
245
+ });
246
+ }
247
+ // ── DM chat ──
248
+ _openDm(user) {
249
+ return __awaiter(this, void 0, void 0, function* () {
250
+ var _a;
251
+ const key = user.userId || user.displayName;
252
+ // Clicking your own avatar opens the group chat (notes to self in context)
253
+ if (key === this.userId) {
254
+ this._openGroupChat();
255
+ return;
256
+ }
257
+ if (this._chats.has(key)) {
258
+ this._chats.get(key).z = ++this._chatZ;
259
+ this.requestUpdate();
260
+ return;
261
+ }
262
+ const offset = [...this._chats.keys()].filter(k => k !== GROUP_CHAT_KEY).length;
263
+ const ix = window.innerWidth - 324 - (offset + 1) * 24;
264
+ const iy = 56 + (offset + 1) * 24;
122
265
  this._chats.set(key, {
123
- user, x: ix, y: iy, savedX: ix, savedY: iy,
266
+ user, conversationId: null, loading: true,
267
+ x: ix, y: iy, savedX: ix, savedY: iy,
124
268
  z: ++this._chatZ, minimized: false, messages: [], draftText: '',
125
269
  });
126
- }
270
+ this.requestUpdate();
271
+ if (!user.userId)
272
+ return;
273
+ try {
274
+ const dmRes = yield fetch('/api/conversations/dm', {
275
+ method: 'POST',
276
+ headers: { 'Content-Type': 'application/json' },
277
+ body: JSON.stringify({ targetUserId: user.userId }),
278
+ });
279
+ const dm = yield dmRes.json();
280
+ const chat = this._chats.get(key);
281
+ if (!chat || !dm.conversationId) {
282
+ if (chat)
283
+ chat.loading = false;
284
+ this.requestUpdate();
285
+ return;
286
+ }
287
+ chat.conversationId = dm.conversationId;
288
+ if ((_a = this.globalSocket) === null || _a === void 0 ? void 0 : _a.connected) {
289
+ this.globalSocket.emit('nk:conversation:join', { conversationId: dm.conversationId });
290
+ }
291
+ yield this._loadMessages(chat);
292
+ }
293
+ catch (e) {
294
+ console.error('[nr-presence] Failed to open DM:', e);
295
+ const chat = this._chats.get(key);
296
+ if (chat)
297
+ chat.loading = false;
298
+ }
299
+ this.requestUpdate();
300
+ });
301
+ }
302
+ // ── Shared helpers ──
303
+ _loadMessages(chat) {
304
+ return __awaiter(this, void 0, void 0, function* () {
305
+ var _a;
306
+ if (!chat.conversationId)
307
+ return;
308
+ try {
309
+ const res = yield fetch(`/api/conversations/${chat.conversationId}/messages`);
310
+ const data = yield res.json();
311
+ if ((_a = data.messages) === null || _a === void 0 ? void 0 : _a.length) {
312
+ chat.messages = data.messages.map((m) => ({
313
+ id: String(m.id),
314
+ senderId: m.sender_id,
315
+ text: m.content,
316
+ timestamp: new Date(m.created_at).getTime(),
317
+ me: m.sender_id === this.userId,
318
+ }));
319
+ }
320
+ }
321
+ catch (e) {
322
+ console.error('[nr-presence] Failed to load messages:', e);
323
+ }
324
+ chat.loading = false;
325
+ });
326
+ }
327
+ _sendMessage(key, text) {
328
+ var _a;
329
+ const chat = this._chats.get(key);
330
+ if (!(chat === null || chat === void 0 ? void 0 : chat.conversationId))
331
+ return;
332
+ // Optimistic local update
333
+ chat.messages = [...chat.messages, {
334
+ id: crypto.randomUUID(),
335
+ senderId: this.userId,
336
+ text,
337
+ timestamp: Date.now(),
338
+ me: true,
339
+ }];
127
340
  this.requestUpdate();
341
+ // Send via global socket
342
+ if ((_a = this.globalSocket) === null || _a === void 0 ? void 0 : _a.connected) {
343
+ this.globalSocket.emit('nk:message:send', {
344
+ conversationId: chat.conversationId,
345
+ content: text,
346
+ type: 'text',
347
+ });
348
+ }
128
349
  }
350
+ // ── Chat panel UI management ──
129
351
  _closeChat(key) {
130
352
  this._chats.delete(key);
131
353
  this.requestUpdate();
@@ -189,9 +411,18 @@ let NrPresenceElement = class NrPresenceElement extends NuralyUIBaseMixin(LitEle
189
411
  return html `
190
412
  <nr-presence-avatars
191
413
  .users=${allUsers}
192
- @user-click=${(e) => this._openChat(e.detail.user)}
414
+ @user-click=${(e) => this._openDm(e.detail.user)}
193
415
  ></nr-presence-avatars>
194
416
 
417
+ ${this.resourceType ? html `
418
+ <button class="group-chat-btn" title="Team chat" @click=${() => this._openGroupChat()}>
419
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
420
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
421
+ </svg>
422
+ ${this._hasUnreadGroup ? html `<span class="unread-dot"></span>` : nothing}
423
+ </button>
424
+ ` : nothing}
425
+
195
426
  ${[...this._chats.entries()].map(([key, c]) => {
196
427
  const minIdx = minimizedList.findIndex(([k]) => k === key);
197
428
  return html `
@@ -203,6 +434,7 @@ let NrPresenceElement = class NrPresenceElement extends NuralyUIBaseMixin(LitEle
203
434
  .minimized=${c.minimized}
204
435
  .minimizedIndex=${minIdx >= 0 ? minIdx : 0}
205
436
  .messages=${c.messages}
437
+ .loading=${c.loading}
206
438
  @focus=${() => { c.z = ++this._chatZ; this.requestUpdate(); }}
207
439
  @drag-start=${(e) => this._onDragStart(key, e.detail.offsetX, e.detail.offsetY)}
208
440
  @minimize=${() => { c.savedX = c.x; c.savedY = c.y; c.minimized = true; this.requestUpdate(); }}
@@ -215,25 +447,28 @@ let NrPresenceElement = class NrPresenceElement extends NuralyUIBaseMixin(LitEle
215
447
  }}
216
448
  @float=${() => { c.minimized = false; c.x = c.savedX; c.y = c.savedY; c.z = ++this._chatZ; this.requestUpdate(); }}
217
449
  @close=${() => this._closeChat(key)}
218
- @send=${(e) => {
219
- c.messages = [...c.messages, {
220
- id: crypto.randomUUID(),
221
- senderId: this.userId,
222
- text: e.detail.text,
223
- timestamp: Date.now(),
224
- me: true,
225
- }];
226
- this.requestUpdate();
227
- }}
450
+ @send=${(e) => this._sendMessage(key, e.detail.text)}
228
451
  ></nr-presence-chat>
229
452
  `;
230
453
  })}
231
-
232
- ${this._chats.size === 0 ? nothing : nothing}
233
454
  `;
234
455
  }
235
456
  };
236
- NrPresenceElement.styles = styles;
457
+ NrPresenceElement.styles = [styles, css `
458
+ .group-chat-btn {
459
+ display: flex; align-items: center; justify-content: center;
460
+ width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--bg, #fff);
461
+ background: var(--accent, #7c3aed); color: #fff; cursor: pointer;
462
+ margin-left: 4px; transition: transform 120ms, background 120ms;
463
+ position: relative;
464
+ }
465
+ .group-chat-btn:hover { transform: scale(1.1); background: var(--accent-hover, #6d28d9); }
466
+ .group-chat-btn svg { width: 14px; height: 14px; }
467
+ .unread-dot {
468
+ position: absolute; top: -2px; right: -2px; width: 8px; height: 8px;
469
+ border-radius: 50%; background: #ef4444; border: 2px solid var(--bg, #fff);
470
+ }
471
+ `];
237
472
  NrPresenceElement.useShadowDom = true;
238
473
  __decorate([
239
474
  property({ type: String })
@@ -244,6 +479,12 @@ __decorate([
244
479
  __decorate([
245
480
  property({ type: String, attribute: 'resource-id' })
246
481
  ], NrPresenceElement.prototype, "resourceId", void 0);
482
+ __decorate([
483
+ property({ type: String, attribute: 'resource-type' })
484
+ ], NrPresenceElement.prototype, "resourceType", void 0);
485
+ __decorate([
486
+ property({ type: String, attribute: 'resource-name' })
487
+ ], NrPresenceElement.prototype, "resourceName", void 0);
247
488
  __decorate([
248
489
  property({ type: String, attribute: 'user-id' })
249
490
  ], NrPresenceElement.prototype, "userId", void 0);
@@ -253,9 +494,15 @@ __decorate([
253
494
  __decorate([
254
495
  property({ type: Array })
255
496
  ], NrPresenceElement.prototype, "extraUsers", void 0);
497
+ __decorate([
498
+ property({ type: Object })
499
+ ], NrPresenceElement.prototype, "globalSocket", void 0);
256
500
  __decorate([
257
501
  state()
258
502
  ], NrPresenceElement.prototype, "_viewers", void 0);
503
+ __decorate([
504
+ state()
505
+ ], NrPresenceElement.prototype, "_hasUnreadGroup", void 0);
259
506
  NrPresenceElement = __decorate([
260
507
  customElement('nr-presence')
261
508
  ], NrPresenceElement);
@@ -19,6 +19,8 @@ export interface PresenceChatMessage {
19
19
  }
20
20
  export interface PresenceChatState {
21
21
  user: PresenceUser;
22
+ conversationId: string | null;
23
+ loading: boolean;
22
24
  x: number;
23
25
  y: number;
24
26
  savedX: number;
@@ -331,13 +331,13 @@ import{css as t,html as e,nothing as o,LitElement as s}from"lit";import{property
331
331
  * @license
332
332
  * Copyright 2023 Nuraly, Laabidi Aymen
333
333
  * SPDX-License-Identifier: MIT
334
- */,d=t=>class extends t{constructor(){super(...arguments),this.handleSystemThemeChange=()=>{this.closest("[data-theme]")||document.documentElement.hasAttribute("data-theme")||this.requestUpdate()}}connectedCallback(){super.connectedCallback(),this.setupThemeObserver(),this.setupDesignSystemObserver(),this.setupSystemThemeListener()}disconnectedCallback(){var t,e,o;super.disconnectedCallback(),null===(t=this.themeObserver)||void 0===t||t.disconnect(),null===(e=this.designSystemObserver)||void 0===e||e.disconnect(),null===(o=this.mediaQuery)||void 0===o||o.removeEventListener("change",this.handleSystemThemeChange)}get currentTheme(){var t,e;const o=(null===(t=this.closest("[data-theme]"))||void 0===t?void 0:t.getAttribute("data-theme"))||document.documentElement.getAttribute("data-theme");return o||((null===(e=window.matchMedia)||void 0===e?void 0:e.call(window,"(prefers-color-scheme: dark)").matches)?"dark":"light")}get currentDesignSystem(){var t;const e=(null===(t=this.closest("[design-system]"))||void 0===t?void 0:t.getAttribute("design-system"))||document.documentElement.getAttribute("design-system");return"carbon"===e?e:"default"}setupThemeObserver(){this.themeObserver=new MutationObserver(()=>{this.requestUpdate()}),this.themeObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]})}setupDesignSystemObserver(){this.designSystemObserver=new MutationObserver(()=>{this.requestUpdate()}),this.designSystemObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["design-system"]})}setupSystemThemeListener(){window.matchMedia&&(this.mediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.mediaQuery.addEventListener("change",this.handleSystemThemeChange))}},h=()=>{var t;return void 0!==globalThis.litElementVersions||"undefined"!=typeof process&&"development"===(null===(t=process.env)||void 0===t?void 0:t.NODE_ENV)||"undefined"!=typeof window&&("localhost"===window.location.hostname||"127.0.0.1"===window.location.hostname)},u=t=>class extends t{constructor(){super(...arguments),this.requiredComponents=[]}validateDependencies(){if(h())for(const t of this.requiredComponents)if(!this.isComponentAvailable(t))throw new Error(`Required component "${t}" is not registered. Please import and register the component before using ${this.tagName.toLowerCase()}. Example: import '@nuralyui/${t}';`)}validateDependenciesWithHandler(t){if(!h())return!0;let e=!0;for(const o of this.requiredComponents)if(!this.isComponentAvailable(o)){e=!1;const s=new Error(`Required component "${o}" is not registered. Please import and register the component before using ${this.tagName.toLowerCase()}.`);t?t(o,s):console.error(s.message)}return e}isComponentAvailable(t){return!!customElements.get(t)}getMissingDependencies(){return this.requiredComponents.filter(t=>!this.isComponentAvailable(t))}areDependenciesAvailable(){return this.requiredComponents.every(t=>this.isComponentAvailable(t))}addRequiredComponent(t){this.requiredComponents.includes(t)||this.requiredComponents.push(t)}removeRequiredComponent(t){const e=this.requiredComponents.indexOf(t);e>-1&&this.requiredComponents.splice(e,1)}},m=t=>class extends t{dispatchCustomEvent(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}dispatchEventWithMetadata(t,e){var o;const s=Object.assign(Object.assign({},e),{timestamp:Date.now(),componentName:(null===(o=this.tagName)||void 0===o?void 0:o.toLowerCase())||"unknown"});this.dispatchCustomEvent(t,s)}dispatchInputEvent(t,e){const o=Object.assign({target:e.target||this,value:e.value,originalEvent:e.originalEvent},e);this.dispatchCustomEvent(t,o)}dispatchFocusEvent(t,e){const o=Object.assign({target:e.target||this,value:e.value,focused:e.focused,cursorPosition:e.cursorPosition,selectedText:e.selectedText},e);this.dispatchCustomEvent(t,o)}dispatchValidationEvent(t,e){var o;const s=Object.assign({target:e.target||this,value:e.value,isValid:null!==(o=e.isValid)&&void 0!==o&&o,error:e.error},e);this.dispatchCustomEvent(t,s)}dispatchActionEvent(t,e){const o=Object.assign({target:e.target||this,action:e.action,previousValue:e.previousValue,newValue:e.newValue},e);this.dispatchCustomEvent(t,o)}isReadonlyKeyAllowed(t){if(t.ctrlKey||t.metaKey){return["KeyA","KeyC"].includes(t.code)}return["Tab","Escape","ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Home","End","PageUp","PageDown"].includes(t.key)}isActivationKey(t){return"Enter"===t.key||" "===t.key}},f=new Set,p=new Map;
334
+ */,d=t=>class extends t{constructor(){super(...arguments),this.handleSystemThemeChange=()=>{this.closest("[data-theme]")||document.documentElement.hasAttribute("data-theme")||this.requestUpdate()}}connectedCallback(){super.connectedCallback(),this.setupThemeObserver(),this.setupDesignSystemObserver(),this.setupSystemThemeListener()}disconnectedCallback(){var t,e,o;super.disconnectedCallback(),null===(t=this.themeObserver)||void 0===t||t.disconnect(),null===(e=this.designSystemObserver)||void 0===e||e.disconnect(),null===(o=this.mediaQuery)||void 0===o||o.removeEventListener("change",this.handleSystemThemeChange)}get currentTheme(){var t,e;const o=(null===(t=this.closest("[data-theme]"))||void 0===t?void 0:t.getAttribute("data-theme"))||document.documentElement.getAttribute("data-theme");return o||((null===(e=window.matchMedia)||void 0===e?void 0:e.call(window,"(prefers-color-scheme: dark)").matches)?"dark":"light")}get currentDesignSystem(){var t;const e=(null===(t=this.closest("[design-system]"))||void 0===t?void 0:t.getAttribute("design-system"))||document.documentElement.getAttribute("design-system");return"carbon"===e?e:"default"}setupThemeObserver(){this.themeObserver=new MutationObserver(()=>{this.requestUpdate()}),this.themeObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]})}setupDesignSystemObserver(){this.designSystemObserver=new MutationObserver(()=>{this.requestUpdate()}),this.designSystemObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["design-system"]})}setupSystemThemeListener(){window.matchMedia&&(this.mediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.mediaQuery.addEventListener("change",this.handleSystemThemeChange))}},h=()=>{var t;return void 0!==globalThis.litElementVersions||"undefined"!=typeof process&&"development"===(null===(t=process.env)||void 0===t?void 0:t.NODE_ENV)||"undefined"!=typeof window&&("localhost"===window.location.hostname||"127.0.0.1"===window.location.hostname)},u=t=>class extends t{constructor(){super(...arguments),this.requiredComponents=[]}validateDependencies(){if(h())for(const t of this.requiredComponents)if(!this.isComponentAvailable(t))throw new Error(`Required component "${t}" is not registered. Please import and register the component before using ${this.tagName.toLowerCase()}. Example: import '@nuralyui/${t}';`)}validateDependenciesWithHandler(t){if(!h())return!0;let e=!0;for(const o of this.requiredComponents)if(!this.isComponentAvailable(o)){e=!1;const s=new Error(`Required component "${o}" is not registered. Please import and register the component before using ${this.tagName.toLowerCase()}.`);t?t(o,s):console.error(s.message)}return e}isComponentAvailable(t){return!!customElements.get(t)}getMissingDependencies(){return this.requiredComponents.filter(t=>!this.isComponentAvailable(t))}areDependenciesAvailable(){return this.requiredComponents.every(t=>this.isComponentAvailable(t))}addRequiredComponent(t){this.requiredComponents.includes(t)||this.requiredComponents.push(t)}removeRequiredComponent(t){const e=this.requiredComponents.indexOf(t);e>-1&&this.requiredComponents.splice(e,1)}},m=t=>class extends t{dispatchCustomEvent(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e,bubbles:!0,composed:!0}))}dispatchEventWithMetadata(t,e){var o;const s=Object.assign(Object.assign({},e),{timestamp:Date.now(),componentName:(null===(o=this.tagName)||void 0===o?void 0:o.toLowerCase())||"unknown"});this.dispatchCustomEvent(t,s)}dispatchInputEvent(t,e){const o=Object.assign({target:e.target||this,value:e.value,originalEvent:e.originalEvent},e);this.dispatchCustomEvent(t,o)}dispatchFocusEvent(t,e){const o=Object.assign({target:e.target||this,value:e.value,focused:e.focused,cursorPosition:e.cursorPosition,selectedText:e.selectedText},e);this.dispatchCustomEvent(t,o)}dispatchValidationEvent(t,e){var o;const s=Object.assign({target:e.target||this,value:e.value,isValid:null!==(o=e.isValid)&&void 0!==o&&o,error:e.error},e);this.dispatchCustomEvent(t,s)}dispatchActionEvent(t,e){const o=Object.assign({target:e.target||this,action:e.action,previousValue:e.previousValue,newValue:e.newValue},e);this.dispatchCustomEvent(t,o)}isReadonlyKeyAllowed(t){if(t.ctrlKey||t.metaKey){return["KeyA","KeyC"].includes(t.code)}return["Tab","Escape","ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Home","End","PageUp","PageDown"].includes(t.key)}isActivationKey(t){return"Enter"===t.key||" "===t.key}},p=new Set,f=new Map;
335
335
  /**
336
336
  * @license
337
337
  * Copyright 2023 Nuraly, Laabidi Aymen
338
338
  * SPDX-License-Identifier: MIT
339
339
  */
340
- const b=t=>{class e extends t{constructor(){super(...arguments),this.t=null}createRenderRoot(){return this.constructor.useShadowDom?super.createRenderRoot():this}connectedCallback(){const t=this.constructor.useShadowDom;if(!t&&null===this.t)for(this.t=[];this.firstChild;)this.t.push(this.removeChild(this.firstChild));if(super.connectedCallback(),!t){const t=this.constructor,e=this.tagName.toLowerCase(),o=t.styles;if(o){const t=v(o);t&&function(t,e,o){var s;if(!p.has(t)){const o=new CSSStyleSheet;o.replaceSync(e),p.set(t,o)}const i=p.get(t),r=`doc:${t}`;if(f.has(r)||(document.adoptedStyleSheets=[...document.adoptedStyleSheets,i],f.add(r)),o){let e=o;for(;e;){const o=e.getRootNode();if(!(o instanceof ShadowRoot))break;{const r=`shadow:${((null===(s=o.host)||void 0===s?void 0:s.tagName)||"").toLowerCase()}:${t}`;f.has(r)||(o.adoptedStyleSheets=[...o.adoptedStyleSheets,i],f.add(r)),e=o.host}}}}(e,t,this)}}}get lightChildren(){return this.t?this.t.filter(t=>!(t instanceof Element&&t.hasAttribute("slot"))):[]}lightChildrenNamed(t){return this.t?this.t.filter(e=>e instanceof Element&&e.getAttribute("slot")===t):[]}}return e.useShadowDom=!1,e};function v(t){return Array.isArray(t)?t.map(t=>v(t)).filter(Boolean).join("\n"):t&&"string"==typeof t.cssText?t.cssText:"string"==typeof t?t:""}
340
+ const b=t=>{class e extends t{constructor(){super(...arguments),this.t=null}createRenderRoot(){return this.constructor.useShadowDom?super.createRenderRoot():this}connectedCallback(){const t=this.constructor.useShadowDom;if(!t&&null===this.t)for(this.t=[];this.firstChild;)this.t.push(this.removeChild(this.firstChild));if(super.connectedCallback(),!t){const t=this.constructor,e=this.tagName.toLowerCase(),o=t.styles;if(o){const t=v(o);t&&function(t,e,o){var s;if(!f.has(t)){const o=new CSSStyleSheet;o.replaceSync(e),f.set(t,o)}const i=f.get(t),r=`doc:${t}`;if(p.has(r)||(document.adoptedStyleSheets=[...document.adoptedStyleSheets,i],p.add(r)),o){let e=o;for(;e;){const o=e.getRootNode();if(!(o instanceof ShadowRoot))break;{const r=`shadow:${((null===(s=o.host)||void 0===s?void 0:s.tagName)||"").toLowerCase()}:${t}`;p.has(r)||(o.adoptedStyleSheets=[...o.adoptedStyleSheets,i],p.add(r)),e=o.host}}}}(e,t,this)}}}get lightChildren(){return this.t?this.t.filter(t=>!(t instanceof Element&&t.hasAttribute("slot"))):[]}lightChildrenNamed(t){return this.t?this.t.filter(e=>e instanceof Element&&e.getAttribute("slot")===t):[]}}return e.useShadowDom=!1,e};function v(t){return Array.isArray(t)?t.map(t=>v(t)).filter(Boolean).join("\n"):t&&"string"==typeof t.cssText?t.cssText:"string"==typeof t?t:""}
341
341
  /**
342
342
  * @license
343
343
  * Copyright 2023 Nuraly, Laabidi Aymen
@@ -349,32 +349,34 @@ const b=t=>{class e extends t{constructor(){super(...arguments),this.t=null}crea
349
349
  * SPDX-License-Identifier: MIT
350
350
  */
351
351
  var x=function(t,e,o,s){for(var i,r=arguments.length,n=r<3?e:null===s?s=Object.getOwnPropertyDescriptor(e,o):s,a=t.length-1;a>=0;a--)(i=t[a])&&(n=(r<3?i(n):r>3?i(e,o,n):i(e,o))||n);return r>3&&n&&Object.defineProperty(e,o,n),n};let k=class extends((t=>u(d(m(b(t)))))(s)){constructor(){super(...arguments),this.requiredComponents=["nr-icon","nr-button"],this.position="top-right",this.maxToasts=5,this.defaultDuration=5e3,this.animation="fade",this.stack=!0,this.autoDismiss=!0,this.toasts=[],this.timeouts=new Map}show(t){var e,o,s;const i="string"==typeof t?{text:t}:t,r={id:this.generateId(),timestamp:Date.now(),text:i.text,content:i.content,type:i.type||"default",duration:null!==(e=i.duration)&&void 0!==e?e:this.defaultDuration,autoDismiss:null!==(o=i.autoDismiss)&&void 0!==o?o:this.autoDismiss,closable:null===(s=i.closable)||void 0===s||s,icon:i.icon||this.getDefaultIcon(i.type),customClass:i.customClass,button:i.button,onClose:i.onClose,onClick:i.onClick};if(this.stack?this.toasts.length>=this.maxToasts&&this.removeToast(this.toasts[0].id):this.clearAll(),this.toasts=[...this.toasts,r],this.emitToastEvent("nr-toast-show",r,"show"),r.autoDismiss&&r.duration&&r.duration>0){const t=window.setTimeout(()=>{this.removeToast(r.id)},r.duration);this.timeouts.set(r.id,t)}return r.id}success(t,e){return this.show({text:t,type:"success",duration:e})}error(t,e){return this.show({text:t,type:"error",duration:e})}warning(t,e){return this.show({text:t,type:"warning",duration:e})}info(t,e){return this.show({text:t,type:"info",duration:e})}removeToast(t){const e=this.toasts.find(e=>e.id===t);if(!e)return;e.removing=!0,this.requestUpdate();const o=this.timeouts.get(t);o&&(clearTimeout(o),this.timeouts.delete(t)),setTimeout(()=>{var o;this.toasts=this.toasts.filter(e=>e.id!==t),this.emitToastEvent("nr-toast-close",e,"close"),null===(o=e.onClose)||void 0===o||o.call(e)},300)}clearAll(){this.toasts.forEach(t=>{const e=this.timeouts.get(t.id);e&&clearTimeout(e)}),this.timeouts.clear(),this.toasts=[]}handleToastClick(t){var e;this.emitToastEvent("nr-toast-click",t,"click"),null===(e=t.onClick)||void 0===e||e.call(t)}handleCloseClick(t,e){t.stopPropagation(),this.removeToast(e.id)}getDefaultIcon(t){switch(t){case"success":return"check-circle";case"error":return"x-circle";case"warning":return"alert-triangle";case"info":return"info";default:return""}}generateId(){return`toast-${Date.now()}-${Math.random().toString(36).substr(2,9)}`}getAnimationClass(t){const e=t.removing?"out":"in";return`toast--${this.animation}-${e}`}emitToastEvent(t,e,o){this.dispatchEvent(new CustomEvent(t,{detail:{toast:e,action:o},bubbles:!0,composed:!0}))}disconnectedCallback(){super.disconnectedCallback(),this.clearAll()}render(){return e`
352
- <div class="toast-container">
352
+ <div class="toast-container" part="container">
353
353
  ${a(this.toasts,t=>t.id,t=>this.renderToast(t))}
354
354
  </div>
355
355
  `}renderToast(t){const s={toast:!0,[`toast--${t.type}`]:!0,[this.getAnimationClass(t)]:!0,[t.customClass||""]:!!t.customClass};return e`
356
356
  <div
357
357
  class=${c(s)}
358
+ part="toast toast-${t.type}"
358
359
  @click=${()=>this.handleToastClick(t)}
359
360
  role="alert"
360
361
  aria-live="polite"
361
362
  >
362
363
  ${t.icon?e`
363
- <div class="toast__icon">
364
+ <div class="toast__icon" part="icon">
364
365
  <nr-icon name=${t.icon} size="medium"></nr-icon>
365
366
  </div>
366
367
  `:o}
367
-
368
- <div class="toast__content">
368
+
369
+ <div class="toast__content" part="content">
369
370
  ${t.content?t.content:e`
370
- <div class="toast__text">${t.text}</div>
371
+ <div class="toast__text" part="text">${t.text}</div>
371
372
  ${t.button?this.renderButton(t.button):o}
372
373
  `}
373
374
  </div>
374
-
375
+
375
376
  ${t.closable?e`
376
377
  <button
377
378
  class="toast__close"
379
+ part="close"
378
380
  @click=${e=>this.handleCloseClick(e,t)}
379
381
  aria-label="Close notification"
380
382
  type="button"
@@ -384,7 +386,7 @@ var x=function(t,e,o,s){for(var i,r=arguments.length,n=r<3?e:null===s?s=Object.g
384
386
  `:o}
385
387
  </div>
386
388
  `}renderButton(t){return e`
387
- <div class="toast__button">
389
+ <div class="toast__button" part="button">
388
390
  <nr-button
389
391
  type=${t.type||"default"}
390
392
  size=${t.size||"small"}
@@ -69,6 +69,14 @@ declare const NrToastElement_base: (new (...args: any[]) => import("@nuralyui/co
69
69
  * @cssproperty --nuraly-toast-error-background - Error toast background
70
70
  * @cssproperty --nuraly-toast-warning-background - Warning toast background
71
71
  * @cssproperty --nuraly-toast-info-background - Info toast background
72
+ *
73
+ * @csspart container - The root list wrapper holding all visible toasts
74
+ * @csspart toast - An individual toast element
75
+ * @csspart icon - The leading icon container (when an icon is set)
76
+ * @csspart content - The content area (text or custom html)
77
+ * @csspart text - The text node (when not using custom content)
78
+ * @csspart button - The action button wrapper (when a button is configured)
79
+ * @csspart close - The close (×) button (when closable)
72
80
  */
73
81
  export declare class NrToastElement extends NrToastElement_base {
74
82
  static useShadowDom: boolean;
@@ -80,6 +80,14 @@ import { DEFAULT_TOAST_DURATION, DEFAULT_MAX_TOASTS, EMPTY_STRING } from './toas
80
80
  * @cssproperty --nuraly-toast-error-background - Error toast background
81
81
  * @cssproperty --nuraly-toast-warning-background - Warning toast background
82
82
  * @cssproperty --nuraly-toast-info-background - Info toast background
83
+ *
84
+ * @csspart container - The root list wrapper holding all visible toasts
85
+ * @csspart toast - An individual toast element
86
+ * @csspart icon - The leading icon container (when an icon is set)
87
+ * @csspart content - The content area (text or custom html)
88
+ * @csspart text - The text node (when not using custom content)
89
+ * @csspart button - The action button wrapper (when a button is configured)
90
+ * @csspart close - The close (×) button (when closable)
83
91
  */
84
92
  let NrToastElement = class NrToastElement extends NuralyUIBaseMixin(LitElement) {
85
93
  constructor() {
@@ -272,7 +280,7 @@ let NrToastElement = class NrToastElement extends NuralyUIBaseMixin(LitElement)
272
280
  }
273
281
  render() {
274
282
  return html `
275
- <div class="toast-container">
283
+ <div class="toast-container" part="container">
276
284
  ${repeat(this.toasts, toast => toast.id, toast => this.renderToast(toast))}
277
285
  </div>
278
286
  `;
@@ -287,28 +295,30 @@ let NrToastElement = class NrToastElement extends NuralyUIBaseMixin(LitElement)
287
295
  return html `
288
296
  <div
289
297
  class=${classMap(classes)}
298
+ part="toast toast-${toast.type}"
290
299
  @click=${() => this.handleToastClick(toast)}
291
300
  role="alert"
292
301
  aria-live="polite"
293
302
  >
294
303
  ${toast.icon ? html `
295
- <div class="toast__icon">
304
+ <div class="toast__icon" part="icon">
296
305
  <nr-icon name=${toast.icon} size="medium"></nr-icon>
297
306
  </div>
298
307
  ` : nothing}
299
-
300
- <div class="toast__content">
308
+
309
+ <div class="toast__content" part="content">
301
310
  ${toast.content
302
311
  ? toast.content
303
312
  : html `
304
- <div class="toast__text">${toast.text}</div>
313
+ <div class="toast__text" part="text">${toast.text}</div>
305
314
  ${toast.button ? this.renderButton(toast.button) : nothing}
306
315
  `}
307
316
  </div>
308
-
317
+
309
318
  ${toast.closable ? html `
310
319
  <button
311
320
  class="toast__close"
321
+ part="close"
312
322
  @click=${(e) => this.handleCloseClick(e, toast)}
313
323
  aria-label="Close notification"
314
324
  type="button"
@@ -325,7 +335,7 @@ let NrToastElement = class NrToastElement extends NuralyUIBaseMixin(LitElement)
325
335
  button.onClick(e);
326
336
  };
327
337
  return html `
328
- <div class="toast__button">
338
+ <div class="toast__button" part="button">
329
339
  <nr-button
330
340
  type=${button.type || 'default'}
331
341
  size=${button.size || 'small'}