@lostgradient/cinder 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +5 -0
  3. package/components.json +1 -1
  4. package/dist/components/chat/chat.schema.js +6 -1
  5. package/dist/components/chat/chat.svelte.d.ts +2 -0
  6. package/dist/components/chat/chat.types.d.ts +6 -0
  7. package/dist/components/chat/container/chat.svelte.d.ts +2 -0
  8. package/dist/components/chat/index.d.ts +1 -1
  9. package/dist/components/chat/index.js +33 -5
  10. package/dist/components/chat/input/chat-input.svelte.d.ts +3 -0
  11. package/dist/components/navigation-bar/index.js +44 -6
  12. package/dist/components/sidebar/index.js +6 -3
  13. package/dist/components/sidebar/sidebar.css +4 -0
  14. package/dist/index.js +81 -12
  15. package/dist/server/components/chat/index.js +1 -1
  16. package/dist/server/components/navigation-bar/index.js +1 -1
  17. package/dist/server/components/sidebar/index.js +1 -1
  18. package/dist/server/index.js +3 -3
  19. package/dist/server/{index.server-zeqv7nrz.js → index.server-5tf22d6e.js} +43 -6
  20. package/dist/server/{index.server-qwknp759.js → index.server-69epaape.js} +6 -3
  21. package/dist/server/{index.server-bvmwnjx6.js → index.server-v4zabrsx.js} +28 -3
  22. package/package.json +3 -1
  23. package/src/components/chat/chat.schema.json +5 -0
  24. package/src/components/chat/chat.schema.ts +6 -0
  25. package/src/components/chat/chat.svelte +8 -0
  26. package/src/components/chat/chat.types.ts +6 -0
  27. package/src/components/chat/container/chat.svelte +10 -1
  28. package/src/components/chat/index.ts +1 -1
  29. package/src/components/chat/input/chat-input.svelte +11 -1
  30. package/src/components/navigation-bar/navigation-bar.svelte +49 -7
  31. package/src/components/sidebar/sidebar.css +4 -0
  32. package/src/components/sidebar/sidebar.svelte +6 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @lostgradient/cinder
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`9686390`](https://github.com/stevekinney/cinder/commit/9686390483ce91eebe82f2f28852e436c1f2724a) Thanks [@stevekinney](https://github.com/stevekinney)! - `Chat` composer public API: added `clearInput()`, `getComposerValue()`, and an `oncomposerinput` callback prop so consumers can read, clear, and observe the composer's plain-text value without reaching into `.chat-input-editor` DOM directly (useful for building slash-command, mention, or autocomplete UX layered on top of the composer).
8
+
9
+ Also re-exported the `ChatAttachment` type from the public `@lostgradient/cinder/chat` entry — previously consumers had to derive it from `ChatSubmitEvent['attachments'][number]`.
10
+
11
+ ### Patch Changes
12
+
13
+ - [#675](https://github.com/stevekinney/cinder/pull/675) [`e55561d`](https://github.com/stevekinney/cinder/commit/e55561db6d348573e29df711c095b2ec18e197ef) Thanks [@stevekinney](https://github.com/stevekinney)! - Close a collapsed top NavigationBar mobile menu after enabled item activation.
14
+
15
+ - [#667](https://github.com/stevekinney/cinder/pull/667) [`d9ce2c3`](https://github.com/stevekinney/cinder/commit/d9ce2c3660805d8b6a7e964f3f671174c61ca819) Thanks [@stevekinney](https://github.com/stevekinney)! - Internal build and validation overhaul: content-hash build caching (with the cache marker excluded from the published tarball), global test cleanup registration, and a lint script split into `lint` and `lint:invariants`.
16
+
3
17
  ## 0.7.0
4
18
 
5
19
  ### Minor Changes
package/README.md CHANGED
@@ -18,6 +18,11 @@ Rich editor, markdown rendering, editor/commentary re-exports, and syntax-highli
18
18
  bun add @milkdown/ctx @milkdown/kit @milkdown/prose @shikijs/engine-oniguruma @shikijs/langs @shikijs/rehype @shikijs/types @types/hast @types/mdast @types/unist comlink hast-util-sanitize js-yaml prosemirror-inputrules prosemirror-model prosemirror-state prosemirror-view rehype-katex rehype-sanitize rehype-stringify remark-gfm remark-html remark-math remark-parse remark-rehype remark-stringify shiki unified unist-util-remove unist-util-visit
19
19
  ```
20
20
 
21
+ `@lostgradient/cinder/markdown-editor` needs the editor and Markdown pipeline subset from that
22
+ list at build time. If `prosemirror-state`, `prosemirror-view`, `@milkdown/kit`, or another editor
23
+ peer is missing, Vite may report the failure through its optional-peer placeholder module instead
24
+ of naming this install step.
25
+
21
26
  ## Quickstart
22
27
 
23
28
  Load the base stylesheet once at your app entry:
package/components.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifestVersion": 1,
4
4
  "package": {
5
5
  "name": "@lostgradient/cinder",
6
- "version": "0.7.0",
6
+ "version": "0.8.0",
7
7
  "framework": "svelte",
8
8
  "frameworkVersionRange": ">=5.56.0 <6",
9
9
  "classPrefix": "cinder-",
@@ -225,6 +225,11 @@ generation. When an adapter is also wired, Chat calls
225
225
  name: "onattachmentremove",
226
226
  reason: "function-or-snippet"
227
227
  },
228
+ {
229
+ name: "oncomposerinput",
230
+ reason: "function-or-snippet",
231
+ description: "Called with the composer's current plain-text value on every composer\ninput event. Lets a consumer build slash-command, mention, or autocomplete\nUX without reaching into `.chat-input-editor` DOM directly."
232
+ },
228
233
  {
229
234
  name: "ondeny",
230
235
  reason: "function-or-snippet",
@@ -316,4 +321,4 @@ export {
316
321
  chat_schema_default as default
317
322
  };
318
323
 
319
- //# debugId=FF251C2AABC0897B64756E2164756E21
324
+ //# debugId=9906999F3FA9B93664756E2164756E21
@@ -21,6 +21,8 @@ declare const Chat: import("svelte").Component<ChatProps, {
21
21
  scrollToBottom: () => void;
22
22
  scrollToTop: () => void;
23
23
  focusInput: () => void;
24
+ clearInput: () => void;
25
+ getComposerValue: () => string;
24
26
  }, "">;
25
27
  type Chat = ReturnType<typeof Chat>;
26
28
  export default Chat;
@@ -228,4 +228,10 @@ export type ChatProps = Omit<HTMLAttributes<HTMLElement>, 'class' | 'onsubmit'>
228
228
  onattachmentadd?: (attachment: ChatAttachment) => void;
229
229
  onattachmentremove?: (attachment: ChatAttachment) => void;
230
230
  onattachmentfailure?: (file: File, error: string) => void;
231
+ /**
232
+ * Called with the composer's current plain-text value on every composer
233
+ * input event. Lets a consumer build slash-command, mention, or autocomplete
234
+ * UX without reaching into `.chat-input-editor` DOM directly.
235
+ */
236
+ oncomposerinput?: (value: string) => void;
231
237
  };
@@ -5,6 +5,8 @@ declare const Chat: import("svelte").Component<ChatProps, {
5
5
  scrollToBottom: () => void;
6
6
  scrollToTop: () => void;
7
7
  focusInput: () => void;
8
+ clearInput: () => void;
9
+ getComposerValue: () => string;
8
10
  beginStreaming: (messageId: string) => void;
9
11
  pushToken: (token: string) => void;
10
12
  endStreaming: () => void;
@@ -15,7 +15,7 @@ export { appendAssistantMessage, appendMessages, appendUserMessage, createConver
15
15
  export type { AssistantMessage, ContainerUploadContent, ConversationHistory, ConversationStatus, ExportOptions, ImageContent, JSONValue, Message, MessageInput, MessageRole, MultiModalContent, RedactedThinkingContent, ServerToolResultContent, ServerToolResultType, ServerToolUseContent, TextContent, ThinkingContent, ToMarkdownOptions, TokenUsage, ToolAction, ToolCall, ToolCallPair, ToolError, ToolErrorCategory, ToolResult, WebSearchToolResultContent, } from './conversation-model.ts';
16
16
  export { formatMessageAsMarkdown, getMessageRoleLabel, getMessageText, messagesToMarkdown, type ChatExportOptions, type DeliveryStatus, } from './utilities';
17
17
  export { ChatAttachmentPreview, ChatInput, deriveAttachmentKind } from './input';
18
- export type { AttachmentKind } from './input';
18
+ export type { AttachmentKind, ChatAttachment } from './input';
19
19
  export { ChatDateSeparator, ChatMessage, MessageAttachments, MessageContent, ToolCallGroup, } from './message';
20
20
  export { DEFAULT_SCROLL_CONFIGURATION, calculateScrollToBottom, calculateUnreadCount, extractTimestamp, findUnreadBoundaryIndex, isAtBottom, shouldShowJumpToLatest, } from './container';
21
21
  export type { ChatScrollStateChangeEvent, ChatStopGeneratingEvent, ChatSubmitEvent, ChatUnreadIndicatorChangeEvent, ScrollConfiguration, ScrollState, } from './container';
@@ -10003,6 +10003,7 @@ var rest_excludes11 = new Set([
10003
10003
  "acceptedTypes",
10004
10004
  "onsubmit",
10005
10005
  "onstop",
10006
+ "oncomposerinput",
10006
10007
  "onattachmentadd",
10007
10008
  "onattachmentremove",
10008
10009
  "onattachmentfailure",
@@ -10202,6 +10203,9 @@ function Chat_input($$anchor, $$props) {
10202
10203
  function handleCompositionEnd() {
10203
10204
  $45.set(isComposing, false);
10204
10205
  }
10206
+ function handleInput() {
10207
+ $$props.oncomposerinput?.(value());
10208
+ }
10205
10209
  function focus() {
10206
10210
  $45.get(editorElement)?.focus();
10207
10211
  }
@@ -10213,6 +10217,9 @@ function Chat_input($$anchor, $$props) {
10213
10217
  function getAttachments() {
10214
10218
  return [...$45.get(attachments)];
10215
10219
  }
10220
+ function getValue() {
10221
+ return value();
10222
+ }
10216
10223
  function addFiles(files) {
10217
10224
  if (!allowAttachments())
10218
10225
  return;
@@ -10221,7 +10228,7 @@ function Chat_input($$anchor, $$props) {
10221
10228
  onDestroy(() => {
10222
10229
  $45.get(attachments).forEach((a) => URL.revokeObjectURL(a.previewUrl));
10223
10230
  });
10224
- var $$exports = { focus, clear, getAttachments, addFiles };
10231
+ var $$exports = { focus, clear, getAttachments, getValue, addFiles };
10225
10232
  var form = root_9();
10226
10233
  $45.attribute_effect(form, ($0) => ({
10227
10234
  class: $0,
@@ -10456,11 +10463,12 @@ function Chat_input($$anchor, $$props) {
10456
10463
  $45.set_attribute(span, "id", $45.get(shortcutDescriptionId));
10457
10464
  $45.set_text(text_4, announcer.message);
10458
10465
  });
10466
+ $45.delegated("input", textarea, handleInput);
10459
10467
  $45.bind_value(textarea, value);
10460
10468
  $45.append($$anchor, form);
10461
10469
  return $45.pop($$exports);
10462
10470
  }
10463
- $45.delegate(["click", "change"]);
10471
+ $45.delegate(["click", "input", "change"]);
10464
10472
  // src/components/chat/container/scroll-utilities.ts
10465
10473
  var DEFAULT_SCROLL_CONFIGURATION = {
10466
10474
  bottomThreshold: 150,
@@ -11898,7 +11906,8 @@ var rest_excludes12 = new Set([
11898
11906
  "onexpandedchange",
11899
11907
  "onattachmentadd",
11900
11908
  "onattachmentremove",
11901
- "onattachmentfailure"
11909
+ "onattachmentfailure",
11910
+ "oncomposerinput"
11902
11911
  ]);
11903
11912
  var root30 = $65.from_html(`<span class="chat-typing-status svelte-932akd"> </span>`);
11904
11913
  var root_121 = $65.from_html(`<span class="chat-typing-dot svelte-932akd" aria-hidden="true"></span> <span class="chat-typing-dot svelte-932akd" aria-hidden="true"></span> <span class="chat-typing-dot svelte-932akd" aria-hidden="true"></span>`, 1);
@@ -12674,6 +12683,12 @@ function Chat($$anchor, $$props) {
12674
12683
  function focusInput() {
12675
12684
  inputRef?.focus();
12676
12685
  }
12686
+ function clearInput() {
12687
+ inputRef?.clear();
12688
+ }
12689
+ function getComposerValue() {
12690
+ return inputRef?.getValue() ?? "";
12691
+ }
12677
12692
  function beginStreaming(messageId) {
12678
12693
  if (streamingScrollRaf !== undefined) {
12679
12694
  cancelAnimationFrame(streamingScrollRaf);
@@ -12739,6 +12754,8 @@ function Chat($$anchor, $$props) {
12739
12754
  scrollToBottom,
12740
12755
  scrollToTop,
12741
12756
  focusInput,
12757
+ clearInput,
12758
+ getComposerValue,
12742
12759
  beginStreaming,
12743
12760
  pushToken,
12744
12761
  endStreaming
@@ -13210,6 +13227,9 @@ function Chat($$anchor, $$props) {
13210
13227
  get onstop() {
13211
13228
  return $65.get($0);
13212
13229
  },
13230
+ get oncomposerinput() {
13231
+ return $$props.oncomposerinput;
13232
+ },
13213
13233
  get onattachmentadd() {
13214
13234
  return $$props.onattachmentadd;
13215
13235
  },
@@ -13278,13 +13298,21 @@ function Chat2($$anchor, $$props) {
13278
13298
  function focusInput() {
13279
13299
  impl?.focusInput();
13280
13300
  }
13301
+ function clearInput() {
13302
+ impl?.clearInput();
13303
+ }
13304
+ function getComposerValue() {
13305
+ return impl?.getComposerValue() ?? "";
13306
+ }
13281
13307
  var $$exports = {
13282
13308
  beginStreaming,
13283
13309
  pushToken,
13284
13310
  endStreaming,
13285
13311
  scrollToBottom,
13286
13312
  scrollToTop,
13287
- focusInput
13313
+ focusInput,
13314
+ clearInput,
13315
+ getComposerValue
13288
13316
  };
13289
13317
  $66.bind_this(Chat($$anchor, $66.spread_props({
13290
13318
  get class() {
@@ -14785,4 +14813,4 @@ export {
14785
14813
  Artifact_panel as ArtifactPanel
14786
14814
  };
14787
14815
 
14788
- //# debugId=2B5157A7C75D81C564756E2164756E21
14816
+ //# debugId=A92B5858E013516164756E2164756E21
@@ -36,6 +36,8 @@ export type ChatInputProps = Omit<HTMLFormAttributes, 'onsubmit' | 'class'> & {
36
36
  onsubmit?: (message: MessageInput, attachments: ChatAttachment[]) => void;
37
37
  /** Called when stop is requested (transforms send button into stop button when sending=true) */
38
38
  onstop?: (() => void) | undefined;
39
+ /** Called with the composer's current plain-text value on every input event. */
40
+ oncomposerinput?: ((value: string) => void) | undefined;
39
41
  /** Called when an attachment is added */
40
42
  onattachmentadd?: ((attachment: ChatAttachment) => void) | undefined;
41
43
  /** Called when an attachment is removed */
@@ -49,6 +51,7 @@ declare const ChatInput: import("svelte").Component<ChatInputProps, {
49
51
  focus: () => void;
50
52
  clear: () => void;
51
53
  getAttachments: () => ChatAttachment[];
54
+ getValue: () => string;
52
55
  addFiles: (files: File[]) => void;
53
56
  }, "value">;
54
57
  type ChatInput = ReturnType<typeof ChatInput>;
@@ -85,6 +85,7 @@ var rest_excludes = new Set([
85
85
  "data-cinder-placement",
86
86
  "data-cinder-label-visibility",
87
87
  "data-cinder-menu-toggle-placement",
88
+ "onclick",
88
89
  "onkeydown"
89
90
  ]);
90
91
  var root = $.from_html(`<div class="cinder-navigation-bar__menu-toggle"><!></div>`);
@@ -98,6 +99,7 @@ function Navigation_bar($$anchor, $$props) {
98
99
  const FALLBACK_ROOT_FONT_SIZE_PX = 16;
99
100
  let placement = $.prop($$props, "placement", 3, "top"), showLabels = $.prop($$props, "showLabels", 3, "always"), menuTogglePlacement = $.prop($$props, "menuTogglePlacement", 3, "after-brand"), mobileMenuOpen = $.prop($$props, "mobileMenuOpen", 15, false), label = $.prop($$props, "label", 3, "Main navigation"), rest = $.rest_props($$props, rest_excludes);
100
101
  const navigationItemSelector = "[data-cinder-navigation-item]";
102
+ const toggleFocusSelector = '.cinder-navigation-bar__menu-toggle button, .cinder-navigation-bar__menu-toggle [href], .cinder-navigation-bar__menu-toggle input, .cinder-navigation-bar__menu-toggle select, .cinder-navigation-bar__menu-toggle textarea, .cinder-navigation-bar__menu-toggle [tabindex]:not([tabindex="-1"])';
101
103
  const isCollapsible = $.derived(() => placement() === "top" && $$props.menuToggle !== undefined);
102
104
  let isMobileLayout = $.state(false);
103
105
  const variant = $.derived(() => placement() === "bottom" ? "mobile" : $.get(isCollapsible) && $.get(isMobileLayout) && mobileMenuOpen() ? "mobile" : "horizontal");
@@ -170,15 +172,50 @@ function Navigation_bar($$anchor, $$props) {
170
172
  return item.getAttribute("aria-disabled") !== "true" && !item.hasAttribute("disabled");
171
173
  }
172
174
  function getEventNavigationItem(event) {
173
- if (!(event.target instanceof HTMLElement) || !itemsRegionElement)
175
+ if (!(event.target instanceof Element) || !itemsRegionElement)
174
176
  return null;
175
177
  const navigationItem = event.target.closest(navigationItemSelector);
176
178
  if (!navigationItem || !itemsRegionElement.contains(navigationItem))
177
179
  return null;
178
- if (navigationItem !== event.target)
179
- return null;
180
180
  return navigationItem;
181
181
  }
182
+ function isModifiedClick(event) {
183
+ return event.button !== 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
184
+ }
185
+ function opensOutsideCurrentPage(item) {
186
+ if (!(item instanceof HTMLAnchorElement))
187
+ return false;
188
+ const target = item.getAttribute("target");
189
+ return item.hasAttribute("download") || target !== null && target.trim() !== "" && target.trim().toLowerCase() !== "_self";
190
+ }
191
+ function canCloseAfterItemActivation(item, event) {
192
+ return $.get(isCollapsible) && $.get(isMobileLayout) && mobileMenuOpen() && isEnabledNavigationItem(item) && !isModifiedClick(event) && !opensOutsideCurrentPage(item);
193
+ }
194
+ function moveFocusBeforeClosingItemsRegion() {
195
+ if (!true_default || !itemsRegionElement)
196
+ return;
197
+ const activeElement = document.activeElement;
198
+ if (activeElement instanceof Element && itemsRegionElement.contains(activeElement)) {
199
+ focusMenuToggle();
200
+ }
201
+ }
202
+ function focusMenuToggle() {
203
+ const focusTarget = toggleElement ?? navigationBarElement?.querySelector(toggleFocusSelector);
204
+ focusTarget?.focus();
205
+ }
206
+ function handleClick(event) {
207
+ if ($$props.onclick) {
208
+ const currentTarget = event.currentTarget instanceof HTMLElement ? event.currentTarget : navigationBarElement;
209
+ $$props.onclick.call(currentTarget, event);
210
+ }
211
+ if (event.defaultPrevented)
212
+ return;
213
+ const navigationItem = getEventNavigationItem(event);
214
+ if (!navigationItem || !canCloseAfterItemActivation(navigationItem, event))
215
+ return;
216
+ moveFocusBeforeClosingItemsRegion();
217
+ mobileMenuOpen(false);
218
+ }
182
219
  function focusAdjacentNavigationItem(currentItem, direction) {
183
220
  const items = getNavigationItems();
184
221
  if (items.length === 0)
@@ -203,11 +240,11 @@ function Navigation_bar($$anchor, $$props) {
203
240
  return;
204
241
  if (event.key === "Escape" && $.get(isCollapsible) && $.get(isMobileLayout) && mobileMenuOpen()) {
205
242
  mobileMenuOpen(false);
206
- toggleElement?.focus();
243
+ focusMenuToggle();
207
244
  return;
208
245
  }
209
246
  const navigationItem = getEventNavigationItem(event);
210
- if (!navigationItem)
247
+ if (!navigationItem || navigationItem !== event.target)
211
248
  return;
212
249
  if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
213
250
  event.preventDefault();
@@ -230,6 +267,7 @@ function Navigation_bar($$anchor, $$props) {
230
267
  "data-cinder-placement": placement(),
231
268
  "data-cinder-label-visibility": showLabels(),
232
269
  "data-cinder-menu-toggle-placement": menuTogglePlacement(),
270
+ onclick: handleClick,
233
271
  onkeydown: handleKeyDown
234
272
  }), [() => classNames("cinder-navigation-bar", $$props.class)]);
235
273
  var node = $.child(nav);
@@ -329,4 +367,4 @@ export {
329
367
  Navigation_bar as NavigationBar
330
368
  };
331
369
 
332
- //# debugId=C52BF290DA6C7F6A64756E2164756E21
370
+ //# debugId=D071FB931C6D395164756E2164756E21
@@ -1053,7 +1053,9 @@ function Sidebar($$anchor, $$props) {
1053
1053
  return label();
1054
1054
  });
1055
1055
  const navigationLabel = $6.derived(() => `${$6.get(validatedLabel)} navigation`);
1056
- const mobile = typeof window === "undefined" || typeof window.matchMedia !== "function" ? { current: false } : new MediaQuery2("(max-width: 47.99rem)", false);
1056
+ const hasMatchMedia = typeof window !== "undefined" && typeof window.matchMedia === "function";
1057
+ const usesSsrResponsiveFallback = !hasMatchMedia;
1058
+ const mobile = hasMatchMedia ? new MediaQuery2("(max-width: 47.99rem)", false) : { current: false };
1057
1059
  const context = {
1058
1060
  get collapsed() {
1059
1061
  return collapsed();
@@ -1103,7 +1105,8 @@ function Sidebar($$anchor, $$props) {
1103
1105
  ...rest,
1104
1106
  class: $0,
1105
1107
  "aria-label": $6.get(validatedLabel),
1106
- "data-cinder-collapsed": collapsed() ? "" : undefined
1108
+ "data-cinder-collapsed": collapsed() ? "" : undefined,
1109
+ "data-cinder-ssr-mobile-fallback": usesSsrResponsiveFallback ? "" : undefined
1107
1110
  }), [
1108
1111
  () => classNames("cinder-sidebar", "cinder-sidebar--desktop", $$props.class)
1109
1112
  ]);
@@ -1130,4 +1133,4 @@ export {
1130
1133
  Sidebar
1131
1134
  };
1132
1135
 
1133
- //# debugId=3DBAC8BB0279502164756E2164756E21
1136
+ //# debugId=13D61E15EB9EC3BE64756E2164756E21
@@ -40,6 +40,10 @@
40
40
  border-inline-end: none;
41
41
  }
42
42
 
43
+ .cinder-sidebar--desktop[data-cinder-ssr-mobile-fallback] {
44
+ display: none;
45
+ }
46
+
43
47
  .cinder-sidebar--desktop[data-cinder-collapsed] {
44
48
  display: none;
45
49
  }
package/dist/index.js CHANGED
@@ -22182,6 +22182,7 @@ var rest_excludes39 = new Set([
22182
22182
  "acceptedTypes",
22183
22183
  "onsubmit",
22184
22184
  "onstop",
22185
+ "oncomposerinput",
22185
22186
  "onattachmentadd",
22186
22187
  "onattachmentremove",
22187
22188
  "onattachmentfailure",
@@ -22381,6 +22382,9 @@ function Chat_input($$anchor, $$props) {
22381
22382
  function handleCompositionEnd() {
22382
22383
  $116.set(isComposing, false);
22383
22384
  }
22385
+ function handleInput() {
22386
+ $$props.oncomposerinput?.(value());
22387
+ }
22384
22388
  function focus() {
22385
22389
  $116.get(editorElement)?.focus();
22386
22390
  }
@@ -22392,6 +22396,9 @@ function Chat_input($$anchor, $$props) {
22392
22396
  function getAttachments() {
22393
22397
  return [...$116.get(attachments)];
22394
22398
  }
22399
+ function getValue() {
22400
+ return value();
22401
+ }
22395
22402
  function addFiles(files) {
22396
22403
  if (!allowAttachments())
22397
22404
  return;
@@ -22400,7 +22407,7 @@ function Chat_input($$anchor, $$props) {
22400
22407
  onDestroy5(() => {
22401
22408
  $116.get(attachments).forEach((a) => URL.revokeObjectURL(a.previewUrl));
22402
22409
  });
22403
- var $$exports = { focus, clear, getAttachments, addFiles };
22410
+ var $$exports = { focus, clear, getAttachments, getValue, addFiles };
22404
22411
  var form = root_96();
22405
22412
  $116.attribute_effect(form, ($0) => ({
22406
22413
  class: $0,
@@ -22635,11 +22642,12 @@ function Chat_input($$anchor, $$props) {
22635
22642
  $116.set_attribute(span, "id", $116.get(shortcutDescriptionId));
22636
22643
  $116.set_text(text_4, announcer.message);
22637
22644
  });
22645
+ $116.delegated("input", textarea, handleInput);
22638
22646
  $116.bind_value(textarea, value);
22639
22647
  $116.append($$anchor, form);
22640
22648
  return $116.pop($$exports);
22641
22649
  }
22642
- $116.delegate(["click", "change"]);
22650
+ $116.delegate(["click", "input", "change"]);
22643
22651
  // src/components/chat/container/scroll-utilities.ts
22644
22652
  var DEFAULT_SCROLL_CONFIGURATION = {
22645
22653
  bottomThreshold: 150,
@@ -24007,7 +24015,8 @@ var rest_excludes40 = new Set([
24007
24015
  "onexpandedchange",
24008
24016
  "onattachmentadd",
24009
24017
  "onattachmentremove",
24010
- "onattachmentfailure"
24018
+ "onattachmentfailure",
24019
+ "oncomposerinput"
24011
24020
  ]);
24012
24021
  var root65 = $133.from_html(`<span class="chat-typing-status svelte-932akd"> </span>`);
24013
24022
  var root_171 = $133.from_html(`<span class="chat-typing-dot svelte-932akd" aria-hidden="true"></span> <span class="chat-typing-dot svelte-932akd" aria-hidden="true"></span> <span class="chat-typing-dot svelte-932akd" aria-hidden="true"></span>`, 1);
@@ -24783,6 +24792,12 @@ function Chat($$anchor, $$props) {
24783
24792
  function focusInput() {
24784
24793
  inputRef?.focus();
24785
24794
  }
24795
+ function clearInput() {
24796
+ inputRef?.clear();
24797
+ }
24798
+ function getComposerValue() {
24799
+ return inputRef?.getValue() ?? "";
24800
+ }
24786
24801
  function beginStreaming(messageId) {
24787
24802
  if (streamingScrollRaf !== undefined) {
24788
24803
  cancelAnimationFrame(streamingScrollRaf);
@@ -24848,6 +24863,8 @@ function Chat($$anchor, $$props) {
24848
24863
  scrollToBottom,
24849
24864
  scrollToTop,
24850
24865
  focusInput,
24866
+ clearInput,
24867
+ getComposerValue,
24851
24868
  beginStreaming,
24852
24869
  pushToken,
24853
24870
  endStreaming
@@ -25319,6 +25336,9 @@ function Chat($$anchor, $$props) {
25319
25336
  get onstop() {
25320
25337
  return $133.get($0);
25321
25338
  },
25339
+ get oncomposerinput() {
25340
+ return $$props.oncomposerinput;
25341
+ },
25322
25342
  get onattachmentadd() {
25323
25343
  return $$props.onattachmentadd;
25324
25344
  },
@@ -25387,13 +25407,21 @@ function Chat2($$anchor, $$props) {
25387
25407
  function focusInput() {
25388
25408
  impl?.focusInput();
25389
25409
  }
25410
+ function clearInput() {
25411
+ impl?.clearInput();
25412
+ }
25413
+ function getComposerValue() {
25414
+ return impl?.getComposerValue() ?? "";
25415
+ }
25390
25416
  var $$exports = {
25391
25417
  beginStreaming,
25392
25418
  pushToken,
25393
25419
  endStreaming,
25394
25420
  scrollToBottom,
25395
25421
  scrollToTop,
25396
- focusInput
25422
+ focusInput,
25423
+ clearInput,
25424
+ getComposerValue
25397
25425
  };
25398
25426
  $134.bind_this(Chat($$anchor, $134.spread_props({
25399
25427
  get class() {
@@ -52075,6 +52103,7 @@ var rest_excludes113 = new Set([
52075
52103
  "data-cinder-placement",
52076
52104
  "data-cinder-label-visibility",
52077
52105
  "data-cinder-menu-toggle-placement",
52106
+ "onclick",
52078
52107
  "onkeydown"
52079
52108
  ]);
52080
52109
  var root164 = $254.from_html(`<div class="cinder-navigation-bar__menu-toggle"><!></div>`);
@@ -52088,6 +52117,7 @@ function Navigation_bar($$anchor, $$props) {
52088
52117
  const FALLBACK_ROOT_FONT_SIZE_PX = 16;
52089
52118
  let placement = $254.prop($$props, "placement", 3, "top"), showLabels = $254.prop($$props, "showLabels", 3, "always"), menuTogglePlacement = $254.prop($$props, "menuTogglePlacement", 3, "after-brand"), mobileMenuOpen = $254.prop($$props, "mobileMenuOpen", 15, false), label = $254.prop($$props, "label", 3, "Main navigation"), rest = $254.rest_props($$props, rest_excludes113);
52090
52119
  const navigationItemSelector = "[data-cinder-navigation-item]";
52120
+ const toggleFocusSelector = '.cinder-navigation-bar__menu-toggle button, .cinder-navigation-bar__menu-toggle [href], .cinder-navigation-bar__menu-toggle input, .cinder-navigation-bar__menu-toggle select, .cinder-navigation-bar__menu-toggle textarea, .cinder-navigation-bar__menu-toggle [tabindex]:not([tabindex="-1"])';
52091
52121
  const isCollapsible = $254.derived(() => placement() === "top" && $$props.menuToggle !== undefined);
52092
52122
  let isMobileLayout = $254.state(false);
52093
52123
  const variant = $254.derived(() => placement() === "bottom" ? "mobile" : $254.get(isCollapsible) && $254.get(isMobileLayout) && mobileMenuOpen() ? "mobile" : "horizontal");
@@ -52160,15 +52190,50 @@ function Navigation_bar($$anchor, $$props) {
52160
52190
  return item.getAttribute("aria-disabled") !== "true" && !item.hasAttribute("disabled");
52161
52191
  }
52162
52192
  function getEventNavigationItem(event24) {
52163
- if (!(event24.target instanceof HTMLElement) || !itemsRegionElement)
52193
+ if (!(event24.target instanceof Element) || !itemsRegionElement)
52164
52194
  return null;
52165
52195
  const navigationItem = event24.target.closest(navigationItemSelector);
52166
52196
  if (!navigationItem || !itemsRegionElement.contains(navigationItem))
52167
52197
  return null;
52168
- if (navigationItem !== event24.target)
52169
- return null;
52170
52198
  return navigationItem;
52171
52199
  }
52200
+ function isModifiedClick(event24) {
52201
+ return event24.button !== 0 || event24.altKey || event24.ctrlKey || event24.metaKey || event24.shiftKey;
52202
+ }
52203
+ function opensOutsideCurrentPage(item) {
52204
+ if (!(item instanceof HTMLAnchorElement))
52205
+ return false;
52206
+ const target = item.getAttribute("target");
52207
+ return item.hasAttribute("download") || target !== null && target.trim() !== "" && target.trim().toLowerCase() !== "_self";
52208
+ }
52209
+ function canCloseAfterItemActivation(item, event24) {
52210
+ return $254.get(isCollapsible) && $254.get(isMobileLayout) && mobileMenuOpen() && isEnabledNavigationItem(item) && !isModifiedClick(event24) && !opensOutsideCurrentPage(item);
52211
+ }
52212
+ function moveFocusBeforeClosingItemsRegion() {
52213
+ if (!true_default || !itemsRegionElement)
52214
+ return;
52215
+ const activeElement = document.activeElement;
52216
+ if (activeElement instanceof Element && itemsRegionElement.contains(activeElement)) {
52217
+ focusMenuToggle();
52218
+ }
52219
+ }
52220
+ function focusMenuToggle() {
52221
+ const focusTarget = toggleElement ?? navigationBarElement?.querySelector(toggleFocusSelector);
52222
+ focusTarget?.focus();
52223
+ }
52224
+ function handleClick(event24) {
52225
+ if ($$props.onclick) {
52226
+ const currentTarget = event24.currentTarget instanceof HTMLElement ? event24.currentTarget : navigationBarElement;
52227
+ $$props.onclick.call(currentTarget, event24);
52228
+ }
52229
+ if (event24.defaultPrevented)
52230
+ return;
52231
+ const navigationItem = getEventNavigationItem(event24);
52232
+ if (!navigationItem || !canCloseAfterItemActivation(navigationItem, event24))
52233
+ return;
52234
+ moveFocusBeforeClosingItemsRegion();
52235
+ mobileMenuOpen(false);
52236
+ }
52172
52237
  function focusAdjacentNavigationItem(currentItem, direction) {
52173
52238
  const items = getNavigationItems();
52174
52239
  if (items.length === 0)
@@ -52193,11 +52258,11 @@ function Navigation_bar($$anchor, $$props) {
52193
52258
  return;
52194
52259
  if (event24.key === "Escape" && $254.get(isCollapsible) && $254.get(isMobileLayout) && mobileMenuOpen()) {
52195
52260
  mobileMenuOpen(false);
52196
- toggleElement?.focus();
52261
+ focusMenuToggle();
52197
52262
  return;
52198
52263
  }
52199
52264
  const navigationItem = getEventNavigationItem(event24);
52200
- if (!navigationItem)
52265
+ if (!navigationItem || navigationItem !== event24.target)
52201
52266
  return;
52202
52267
  if (event24.key === "ArrowLeft" || event24.key === "ArrowRight") {
52203
52268
  event24.preventDefault();
@@ -52220,6 +52285,7 @@ function Navigation_bar($$anchor, $$props) {
52220
52285
  "data-cinder-placement": placement(),
52221
52286
  "data-cinder-label-visibility": showLabels(),
52222
52287
  "data-cinder-menu-toggle-placement": menuTogglePlacement(),
52288
+ onclick: handleClick,
52223
52289
  onkeydown: handleKeyDown
52224
52290
  }), [() => classNames("cinder-navigation-bar", $$props.class)]);
52225
52291
  var node = $254.child(nav);
@@ -63660,7 +63726,9 @@ function Sidebar($$anchor, $$props) {
63660
63726
  return label();
63661
63727
  });
63662
63728
  const navigationLabel = $284.derived(() => `${$284.get(validatedLabel)} navigation`);
63663
- const mobile = typeof window === "undefined" || typeof window.matchMedia !== "function" ? { current: false } : new MediaQuery2("(max-width: 47.99rem)", false);
63729
+ const hasMatchMedia = typeof window !== "undefined" && typeof window.matchMedia === "function";
63730
+ const usesSsrResponsiveFallback = !hasMatchMedia;
63731
+ const mobile = hasMatchMedia ? new MediaQuery2("(max-width: 47.99rem)", false) : { current: false };
63664
63732
  const context = {
63665
63733
  get collapsed() {
63666
63734
  return collapsed();
@@ -63710,7 +63778,8 @@ function Sidebar($$anchor, $$props) {
63710
63778
  ...rest,
63711
63779
  class: $0,
63712
63780
  "aria-label": $284.get(validatedLabel),
63713
- "data-cinder-collapsed": collapsed() ? "" : undefined
63781
+ "data-cinder-collapsed": collapsed() ? "" : undefined,
63782
+ "data-cinder-ssr-mobile-fallback": usesSsrResponsiveFallback ? "" : undefined
63714
63783
  }), [
63715
63784
  () => classNames("cinder-sidebar", "cinder-sidebar--desktop", $$props.class)
63716
63785
  ]);
@@ -74446,4 +74515,4 @@ export {
74446
74515
  AccessGateExport as AccessGate
74447
74516
  };
74448
74517
 
74449
- //# debugId=601DF5555E6E0A1764756E2164756E21
74518
+ //# debugId=D4AB81D9615DF39E64756E2164756E21
@@ -23,7 +23,7 @@ import {
23
23
  findUnreadBoundaryIndex,
24
24
  isAtBottom,
25
25
  shouldShowJumpToLatest
26
- } from "../../index.server-bvmwnjx6.js";
26
+ } from "../../index.server-v4zabrsx.js";
27
27
  import"../../index.server-qkfd0qa7.js";
28
28
  import"../../index.server-kb6w1a7c.js";
29
29
  import"../../index.server-317enkqq.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Navigation_bar,
3
3
  navigation_bar_default
4
- } from "../../index.server-zeqv7nrz.js";
4
+ } from "../../index.server-5tf22d6e.js";
5
5
  import"../../index.server-ge07r1p8.js";
6
6
  import"../../index.server-z7nvkz8v.js";
7
7
  import"../../index.server-0v6na3yp.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Sidebar,
3
3
  sidebar_default
4
- } from "../../index.server-qwknp759.js";
4
+ } from "../../index.server-69epaape.js";
5
5
  import"../../index.server-9jete4xx.js";
6
6
  import"../../index.server-6zgzghtm.js";
7
7
  import"../../index.server-78d33trm.js";
@@ -164,7 +164,7 @@ import"./index.server-d6j2jmqg.js";
164
164
  import"./index.server-amfq2v2p.js";
165
165
  import {
166
166
  sidebar_default
167
- } from "./index.server-qwknp759.js";
167
+ } from "./index.server-69epaape.js";
168
168
  import {
169
169
  skeleton_default
170
170
  } from "./index.server-5fypkt27.js";
@@ -247,7 +247,7 @@ import {
247
247
  } from "./index.server-dj47xy3x.js";
248
248
  import {
249
249
  navigation_bar_default
250
- } from "./index.server-zeqv7nrz.js";
250
+ } from "./index.server-5tf22d6e.js";
251
251
  import {
252
252
  navigation_item_default
253
253
  } from "./index.server-ncdt0xb7.js";
@@ -566,7 +566,7 @@ import {
566
566
  } from "./index.server-tbyd4re7.js";
567
567
  import {
568
568
  chat_default
569
- } from "./index.server-bvmwnjx6.js";
569
+ } from "./index.server-v4zabrsx.js";
570
570
  import"./index.server-qkfd0qa7.js";
571
571
  import {
572
572
  useIntersection
@@ -28,12 +28,14 @@ function Navigation_bar($$renderer, $$props) {
28
28
  "data-cinder-placement": _dataCinderPlacement,
29
29
  "data-cinder-label-visibility": _dataCinderLabelVisibility,
30
30
  "data-cinder-menu-toggle-placement": _dataCinderMenuTogglePlacement,
31
+ onclick: consumerOnClick,
31
32
  onkeydown: consumerOnKeyDown,
32
33
  $$slots,
33
34
  $$events,
34
35
  ...rest
35
36
  } = $$props;
36
37
  const navigationItemSelector = "[data-cinder-navigation-item]";
38
+ const toggleFocusSelector = '.cinder-navigation-bar__menu-toggle button, .cinder-navigation-bar__menu-toggle [href], .cinder-navigation-bar__menu-toggle input, .cinder-navigation-bar__menu-toggle select, .cinder-navigation-bar__menu-toggle textarea, .cinder-navigation-bar__menu-toggle [tabindex]:not([tabindex="-1"])';
37
39
  const isCollapsible = $.derived(() => placement === "top" && menuToggle !== undefined);
38
40
  let isMobileLayout = false;
39
41
  const variant = $.derived(() => placement === "bottom" ? "mobile" : isCollapsible() && isMobileLayout && mobileMenuOpen ? "mobile" : "horizontal");
@@ -67,15 +69,50 @@ function Navigation_bar($$renderer, $$props) {
67
69
  return item.getAttribute("aria-disabled") !== "true" && !item.hasAttribute("disabled");
68
70
  }
69
71
  function getEventNavigationItem(event) {
70
- if (!(event.target instanceof HTMLElement) || !itemsRegionElement)
72
+ if (!(event.target instanceof Element) || !itemsRegionElement)
71
73
  return null;
72
74
  const navigationItem = event.target.closest(navigationItemSelector);
73
75
  if (!navigationItem || !itemsRegionElement.contains(navigationItem))
74
76
  return null;
75
- if (navigationItem !== event.target)
76
- return null;
77
77
  return navigationItem;
78
78
  }
79
+ function isModifiedClick(event) {
80
+ return event.button !== 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
81
+ }
82
+ function opensOutsideCurrentPage(item) {
83
+ if (!(item instanceof HTMLAnchorElement))
84
+ return false;
85
+ const target = item.getAttribute("target");
86
+ return item.hasAttribute("download") || target !== null && target.trim() !== "" && target.trim().toLowerCase() !== "_self";
87
+ }
88
+ function canCloseAfterItemActivation(item, event) {
89
+ return isCollapsible() && isMobileLayout && mobileMenuOpen && isEnabledNavigationItem(item) && !isModifiedClick(event) && !opensOutsideCurrentPage(item);
90
+ }
91
+ function moveFocusBeforeClosingItemsRegion() {
92
+ if (!false_default || !itemsRegionElement)
93
+ return;
94
+ const activeElement = document.activeElement;
95
+ if (activeElement instanceof Element && itemsRegionElement.contains(activeElement)) {
96
+ focusMenuToggle();
97
+ }
98
+ }
99
+ function focusMenuToggle() {
100
+ const focusTarget = toggleElement ?? navigationBarElement?.querySelector(toggleFocusSelector);
101
+ focusTarget?.focus();
102
+ }
103
+ function handleClick(event) {
104
+ if (consumerOnClick) {
105
+ const currentTarget = event.currentTarget instanceof HTMLElement ? event.currentTarget : navigationBarElement;
106
+ consumerOnClick.call(currentTarget, event);
107
+ }
108
+ if (event.defaultPrevented)
109
+ return;
110
+ const navigationItem = getEventNavigationItem(event);
111
+ if (!navigationItem || !canCloseAfterItemActivation(navigationItem, event))
112
+ return;
113
+ moveFocusBeforeClosingItemsRegion();
114
+ mobileMenuOpen = false;
115
+ }
79
116
  function focusAdjacentNavigationItem(currentItem, direction) {
80
117
  const items2 = getNavigationItems();
81
118
  if (items2.length === 0)
@@ -100,11 +137,11 @@ function Navigation_bar($$renderer, $$props) {
100
137
  return;
101
138
  if (event.key === "Escape" && isCollapsible() && isMobileLayout && mobileMenuOpen) {
102
139
  mobileMenuOpen = false;
103
- toggleElement?.focus();
140
+ focusMenuToggle();
104
141
  return;
105
142
  }
106
143
  const navigationItem = getEventNavigationItem(event);
107
- if (!navigationItem)
144
+ if (!navigationItem || navigationItem !== event.target)
108
145
  return;
109
146
  if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
110
147
  event.preventDefault();
@@ -181,4 +218,4 @@ function Navigation_bar($$renderer, $$props) {
181
218
  var navigation_bar_default = Navigation_bar;
182
219
  export { Navigation_bar, navigation_bar_default };
183
220
 
184
- //# debugId=414D38B61FBCF6F064756E2164756E21
221
+ //# debugId=7CE77ABFF189F35064756E2164756E21
@@ -44,7 +44,9 @@ function Sidebar($$renderer, $$props) {
44
44
  return label;
45
45
  });
46
46
  const navigationLabel = $.derived(() => `${validatedLabel()} navigation`);
47
- const mobile = typeof window === "undefined" || typeof window.matchMedia !== "function" ? { current: false } : new MediaQuery("(max-width: 47.99rem)", false);
47
+ const hasMatchMedia = typeof window !== "undefined" && typeof window.matchMedia === "function";
48
+ const usesSsrResponsiveFallback = !hasMatchMedia;
49
+ const mobile = hasMatchMedia ? new MediaQuery("(max-width: 47.99rem)", false) : { current: false };
48
50
  const context = {
49
51
  get collapsed() {
50
52
  return collapsed;
@@ -117,7 +119,8 @@ function Sidebar($$renderer, $$props) {
117
119
  ...rest,
118
120
  class: $.clsx(classNames("cinder-sidebar", "cinder-sidebar--desktop", className)),
119
121
  "aria-label": validatedLabel(),
120
- "data-cinder-collapsed": collapsed ? "" : undefined
122
+ "data-cinder-collapsed": collapsed ? "" : undefined,
123
+ "data-cinder-ssr-mobile-fallback": usesSsrResponsiveFallback ? "" : undefined
121
124
  })}>`);
122
125
  sidebarContents($$renderer3, false);
123
126
  $$renderer3.push(`<!----></aside>`);
@@ -138,4 +141,4 @@ function Sidebar($$renderer, $$props) {
138
141
  var sidebar_default = Sidebar;
139
142
  export { Sidebar, sidebar_default };
140
143
 
141
- //# debugId=7384BF809C61CBDC64756E2164756E21
144
+ //# debugId=4AE415A204BE6FC264756E2164756E21
@@ -1267,6 +1267,7 @@ function Chat_input($$renderer, $$props) {
1267
1267
  ],
1268
1268
  onsubmit,
1269
1269
  onstop,
1270
+ oncomposerinput,
1270
1271
  onattachmentadd,
1271
1272
  onattachmentremove,
1272
1273
  onattachmentfailure,
@@ -1441,6 +1442,9 @@ function Chat_input($$renderer, $$props) {
1441
1442
  function handleCompositionEnd() {
1442
1443
  isComposing = false;
1443
1444
  }
1445
+ function handleInput() {
1446
+ oncomposerinput?.(value);
1447
+ }
1444
1448
  function focus() {
1445
1449
  editorElement?.focus();
1446
1450
  }
@@ -1452,6 +1456,9 @@ function Chat_input($$renderer, $$props) {
1452
1456
  function getAttachments() {
1453
1457
  return [...attachments];
1454
1458
  }
1459
+ function getValue() {
1460
+ return value;
1461
+ }
1455
1462
  function addFiles(files) {
1456
1463
  if (!allowAttachments)
1457
1464
  return;
@@ -1565,7 +1572,7 @@ function Chat_input($$renderer, $$props) {
1565
1572
  $$renderer2.push("<!--[-1-->");
1566
1573
  }
1567
1574
  $$renderer2.push(`<!--]--> <div aria-live="polite" aria-atomic="true" class="sr-only svelte-1tzweuh">${$19.escape(announcer.message)}</div></form>`);
1568
- $19.bind_props($$props, { value, focus, clear, getAttachments, addFiles });
1575
+ $19.bind_props($$props, { value, focus, clear, getAttachments, getValue, addFiles });
1569
1576
  }, Chat_input);
1570
1577
  }
1571
1578
  // src/components/chat/container/scroll-utilities.ts
@@ -2731,6 +2738,7 @@ function Chat($$renderer, $$props) {
2731
2738
  onattachmentadd,
2732
2739
  onattachmentremove,
2733
2740
  onattachmentfailure,
2741
+ oncomposerinput,
2734
2742
  $$slots,
2735
2743
  $$events,
2736
2744
  ...rest
@@ -3356,6 +3364,12 @@ function Chat($$renderer, $$props) {
3356
3364
  function focusInput() {
3357
3365
  inputRef?.focus();
3358
3366
  }
3367
+ function clearInput() {
3368
+ inputRef?.clear();
3369
+ }
3370
+ function getComposerValue() {
3371
+ return inputRef?.getValue() ?? "";
3372
+ }
3359
3373
  function beginStreaming(messageId) {
3360
3374
  if (streamingScrollRaf !== undefined) {
3361
3375
  cancelAnimationFrame(streamingScrollRaf);
@@ -3646,6 +3660,7 @@ function Chat($$renderer, $$props) {
3646
3660
  sending: streaming,
3647
3661
  allowAttachments: allowAttachments(),
3648
3662
  onstop: streaming ? handleStopGenerating : undefined,
3663
+ oncomposerinput,
3649
3664
  onattachmentadd,
3650
3665
  onattachmentremove,
3651
3666
  onattachmentfailure
@@ -3665,6 +3680,8 @@ function Chat($$renderer, $$props) {
3665
3680
  scrollToBottom,
3666
3681
  scrollToTop,
3667
3682
  focusInput,
3683
+ clearInput,
3684
+ getComposerValue,
3668
3685
  beginStreaming,
3669
3686
  pushToken,
3670
3687
  endStreaming
@@ -3696,6 +3713,12 @@ function Chat2($$renderer, $$props) {
3696
3713
  function focusInput() {
3697
3714
  impl?.focusInput();
3698
3715
  }
3716
+ function clearInput() {
3717
+ impl?.clearInput();
3718
+ }
3719
+ function getComposerValue() {
3720
+ return impl?.getComposerValue() ?? "";
3721
+ }
3699
3722
  Chat($$renderer2, $36.spread_props([{ class: mergedClassName() }, rest]));
3700
3723
  $36.bind_props($$props, {
3701
3724
  beginStreaming,
@@ -3703,7 +3726,9 @@ function Chat2($$renderer, $$props) {
3703
3726
  endStreaming,
3704
3727
  scrollToBottom,
3705
3728
  scrollToTop,
3706
- focusInput
3729
+ focusInput,
3730
+ clearInput,
3731
+ getComposerValue
3707
3732
  });
3708
3733
  }, Chat2);
3709
3734
  }
@@ -3794,4 +3819,4 @@ function Chat_artifact_layout($$renderer, $$props) {
3794
3819
  var chat_default = Chat2;
3795
3820
  export { Chat_date_separator, Message_content, Tool_call_group, Message_attachments, Chat_message, deriveAttachmentKind, Chat_attachment_preview, Chat_input, DEFAULT_SCROLL_CONFIGURATION, isAtBottom, shouldShowJumpToLatest, calculateUnreadCount, findUnreadBoundaryIndex, calculateScrollToBottom, extractTimestamp, Chat2 as Chat, createConversation, appendMessages2 as appendMessages, appendUserMessage2 as appendUserMessage, appendAssistantMessage2 as appendAssistantMessage, Artifact_panel, Artifact_viewer, Chat_artifact_layout, chat_default };
3796
3821
 
3797
- //# debugId=C6DF556ADAD5FCBD64756E2164756E21
3822
+ //# debugId=48E7964ABAFE717A64756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lostgradient/cinder",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "A Svelte 5 component library and design system focused on accessibility, theming, and per-component tree-shaking.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -6427,10 +6427,12 @@
6427
6427
  "@testing-library/jest-dom": "^6.9.1",
6428
6428
  "@testing-library/svelte": "^5.2.7",
6429
6429
  "@types/bun": "^1.3.11",
6430
+ "@types/js-yaml": "^4.0.9",
6430
6431
  "@types/qrcode": "^1.5.6",
6431
6432
  "chalk": "^5.6.2",
6432
6433
  "change-case": "^5.4.4",
6433
6434
  "happy-dom": "^15.11.7",
6435
+ "js-yaml": "^4.1.0",
6434
6436
  "lucide-svelte": "^0.503.0",
6435
6437
  "oxlint": "^1.56.0",
6436
6438
  "oxlint-tsgolint": "^0.17.0",
@@ -164,6 +164,11 @@
164
164
  "name": "onattachmentremove",
165
165
  "reason": "function-or-snippet"
166
166
  },
167
+ {
168
+ "name": "oncomposerinput",
169
+ "reason": "function-or-snippet",
170
+ "description": "Called with the composer's current plain-text value on every composer\ninput event. Lets a consumer build slash-command, mention, or autocomplete\nUX without reaching into `.chat-input-editor` DOM directly."
171
+ },
167
172
  {
168
173
  "name": "ondeny",
169
174
  "reason": "function-or-snippet",
@@ -191,6 +191,12 @@ const schema = {
191
191
  name: 'onattachmentremove',
192
192
  reason: 'function-or-snippet',
193
193
  },
194
+ {
195
+ name: 'oncomposerinput',
196
+ reason: 'function-or-snippet',
197
+ description:
198
+ "Called with the composer's current plain-text value on every composer\ninput event. Lets a consumer build slash-command, mention, or autocomplete\nUX without reaching into `.chat-input-editor` DOM directly.",
199
+ },
194
200
  {
195
201
  name: 'ondeny',
196
202
  reason: 'function-or-snippet',
@@ -42,6 +42,14 @@ export function scrollToTop() {
42
42
  export function focusInput() {
43
43
  impl?.focusInput();
44
44
  }
45
+ /** Clear the composer's current content. No-op until mounted. */
46
+ export function clearInput() {
47
+ impl?.clearInput();
48
+ }
49
+ /** Read the composer's current plain-text value. Returns '' until mounted. */
50
+ export function getComposerValue() {
51
+ return impl?.getComposerValue() ?? '';
52
+ }
45
53
  </script>
46
54
 
47
55
  <ChatImplementation bind:this={impl} class={mergedClassName} {...rest} />
@@ -243,4 +243,10 @@ export type ChatProps = Omit<HTMLAttributes<HTMLElement>, 'class' | 'onsubmit'>
243
243
  onattachmentadd?: (attachment: ChatAttachment) => void;
244
244
  onattachmentremove?: (attachment: ChatAttachment) => void;
245
245
  onattachmentfailure?: (file: File, error: string) => void;
246
+ /**
247
+ * Called with the composer's current plain-text value on every composer
248
+ * input event. Lets a consumer build slash-command, mention, or autocomplete
249
+ * UX without reaching into `.chat-input-editor` DOM directly.
250
+ */
251
+ oncomposerinput?: (value: string) => void;
246
252
  };
@@ -26,7 +26,7 @@ import { useChatReadReceipts } from './use-chat-read-receipts.svelte.ts';
26
26
  import ChatParticipantTyping from './chat-participant-typing.svelte';
27
27
  import ChatReadReceipt from '../message/chat-read-receipt.svelte';
28
28
  const noopAttachment = () => { };
29
- let { id, conversation, atBottom = $bindable(true), unreadCount = $bindable(0), newMessageIndicatorVisible = $bindable(false), class: className, surfaceMode = 'default', density = 'comfortable', variant = 'bubble', bottomThreshold = DEFAULT_SCROLL_CONFIGURATION.bottomThreshold, jumpThreshold = DEFAULT_SCROLL_CONFIGURATION.jumpThreshold, streaming = false, streamingStatus, capabilities, virtualized = false, virtualizationEstimatedRowHeight = 88, virtualizationOverscan = 3, virtualizationInitialHeight = 640, moreHistoryAvailable = true, loadEarlierLabel = 'Load earlier messages', loadingEarlierLabel = 'Loading earlier messages', header, empty, emptyPrompts, messageActions, messageStatus, row, messagePart, viewportAttachment, typingParticipants, readReceipts, adapter, onadaptererror, onpushmessage, ontypingchange, onreadreceipt, onsubmit, onretry, onedit, onapprove, ondeny, messageReasoning, messageSteps, messageSuggestions, onsuggestionselect, onloadhistory, onstopgenerating, onjumptolatest, onscrollstatechange, onunreadindicatorchange, onexpandedchange, onattachmentadd, onattachmentremove, onattachmentfailure, ...rest } = $props();
29
+ let { id, conversation, atBottom = $bindable(true), unreadCount = $bindable(0), newMessageIndicatorVisible = $bindable(false), class: className, surfaceMode = 'default', density = 'comfortable', variant = 'bubble', bottomThreshold = DEFAULT_SCROLL_CONFIGURATION.bottomThreshold, jumpThreshold = DEFAULT_SCROLL_CONFIGURATION.jumpThreshold, streaming = false, streamingStatus, capabilities, virtualized = false, virtualizationEstimatedRowHeight = 88, virtualizationOverscan = 3, virtualizationInitialHeight = 640, moreHistoryAvailable = true, loadEarlierLabel = 'Load earlier messages', loadingEarlierLabel = 'Loading earlier messages', header, empty, emptyPrompts, messageActions, messageStatus, row, messagePart, viewportAttachment, typingParticipants, readReceipts, adapter, onadaptererror, onpushmessage, ontypingchange, onreadreceipt, onsubmit, onretry, onedit, onapprove, ondeny, messageReasoning, messageSteps, messageSuggestions, onsuggestionselect, onloadhistory, onstopgenerating, onjumptolatest, onscrollstatechange, onunreadindicatorchange, onexpandedchange, onattachmentadd, onattachmentremove, onattachmentfailure, oncomposerinput, ...rest } = $props();
30
30
  // ==========================================================================
31
31
  // Refs and Internal State
32
32
  // ==========================================================================
@@ -1061,6 +1061,14 @@ export function scrollToTop() {
1061
1061
  export function focusInput() {
1062
1062
  inputRef?.focus();
1063
1063
  }
1064
+ /** Clear the composer's current content. */
1065
+ export function clearInput() {
1066
+ inputRef?.clear();
1067
+ }
1068
+ /** Read the composer's current plain-text value. */
1069
+ export function getComposerValue() {
1070
+ return inputRef?.getValue() ?? '';
1071
+ }
1064
1072
  /**
1065
1073
  * Begin streaming content for a specific message.
1066
1074
  * The message should already exist in the conversation.
@@ -1405,6 +1413,7 @@ function virtualRowAttachment(row) {
1405
1413
  sending={streaming}
1406
1414
  {allowAttachments}
1407
1415
  onstop={streaming ? handleStopGenerating : undefined}
1416
+ {oncomposerinput}
1408
1417
  {onattachmentadd}
1409
1418
  {onattachmentremove}
1410
1419
  {onattachmentfailure}
@@ -81,7 +81,7 @@ export {
81
81
 
82
82
  // Input
83
83
  export { ChatAttachmentPreview, ChatInput, deriveAttachmentKind } from './input';
84
- export type { AttachmentKind } from './input';
84
+ export type { AttachmentKind, ChatAttachment } from './input';
85
85
 
86
86
  // Message
87
87
  export {
@@ -55,7 +55,7 @@ acceptedTypes = [
55
55
  'application/x-sh',
56
56
  'application/sql',
57
57
  'application/toml',
58
- ], onsubmit, onstop, onattachmentadd, onattachmentremove, onattachmentfailure, actions, ...rest } = $props();
58
+ ], onsubmit, onstop, oncomposerinput, onattachmentadd, onattachmentremove, onattachmentfailure, actions, ...rest } = $props();
59
59
  const resolvedComposerLabel = $derived(composerLabel.trim() || 'Message');
60
60
  // Derived: show stop button when sending and onstop is provided
61
61
  const showStopButton = $derived(sending && onstop !== undefined);
@@ -284,6 +284,12 @@ function handleCompositionStart() {
284
284
  function handleCompositionEnd() {
285
285
  isComposing = false;
286
286
  }
287
+ // Fires after `bind:value` has already applied the textarea's new value
288
+ // (Svelte merges the binding's own input listener with this handler on the
289
+ // same event), so `value` here is always current — never one keystroke stale.
290
+ function handleInput() {
291
+ oncomposerinput?.(value);
292
+ }
287
293
  // =========================================================================
288
294
  // Imperative API
289
295
  // =========================================================================
@@ -298,6 +304,9 @@ export function clear() {
298
304
  export function getAttachments() {
299
305
  return [...attachments];
300
306
  }
307
+ export function getValue() {
308
+ return value;
309
+ }
301
310
  export function addFiles(files) {
302
311
  if (!allowAttachments)
303
312
  return;
@@ -358,6 +367,7 @@ onDestroy(() => {
358
367
  bind:this={editorElement}
359
368
  id={`${id}-editor`}
360
369
  bind:value
370
+ oninput={handleInput}
361
371
  {placeholder}
362
372
  aria-label={resolvedComposerLabel}
363
373
  {disabled}
@@ -10,8 +10,9 @@ const FALLBACK_ROOT_FONT_SIZE_PX = 16;
10
10
  const regionId = $props.id();
11
11
  let { class: className, placement = 'top', showLabels = 'always', menuTogglePlacement = 'after-brand', brand, items, actions, menuToggle, mobileMenuOpen = $bindable(false), label = 'Main navigation',
12
12
  // Strip these from rest so they cannot collide with internal attributes.
13
- 'aria-label': _ariaLabel, 'data-collapsible': _dataCollapsible, 'data-cinder-placement': _dataCinderPlacement, 'data-cinder-label-visibility': _dataCinderLabelVisibility, 'data-cinder-menu-toggle-placement': _dataCinderMenuTogglePlacement, onkeydown: consumerOnKeyDown, ...rest } = $props();
13
+ 'aria-label': _ariaLabel, 'data-collapsible': _dataCollapsible, 'data-cinder-placement': _dataCinderPlacement, 'data-cinder-label-visibility': _dataCinderLabelVisibility, 'data-cinder-menu-toggle-placement': _dataCinderMenuTogglePlacement, onclick: consumerOnClick, onkeydown: consumerOnKeyDown, ...rest } = $props();
14
14
  const navigationItemSelector = '[data-cinder-navigation-item]';
15
+ const toggleFocusSelector = '.cinder-navigation-bar__menu-toggle button, .cinder-navigation-bar__menu-toggle [href], .cinder-navigation-bar__menu-toggle input, .cinder-navigation-bar__menu-toggle select, .cinder-navigation-bar__menu-toggle textarea, .cinder-navigation-bar__menu-toggle [tabindex]:not([tabindex="-1"])';
15
16
  const isCollapsible = $derived(placement === 'top' && menuToggle !== undefined);
16
17
  let isMobileLayout = $state(false);
17
18
  const variant = $derived(placement === 'bottom'
@@ -95,15 +96,56 @@ function isEnabledNavigationItem(item) {
95
96
  return item.getAttribute('aria-disabled') !== 'true' && !item.hasAttribute('disabled');
96
97
  }
97
98
  function getEventNavigationItem(event) {
98
- if (!(event.target instanceof HTMLElement) || !itemsRegionElement)
99
+ if (!(event.target instanceof Element) || !itemsRegionElement)
99
100
  return null;
100
101
  const navigationItem = event.target.closest(navigationItemSelector);
101
102
  if (!navigationItem || !itemsRegionElement.contains(navigationItem))
102
103
  return null;
103
- if (navigationItem !== event.target)
104
- return null;
105
104
  return navigationItem;
106
105
  }
106
+ function isModifiedClick(event) {
107
+ return event.button !== 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
108
+ }
109
+ function opensOutsideCurrentPage(item) {
110
+ if (!(item instanceof HTMLAnchorElement))
111
+ return false;
112
+ const target = item.getAttribute('target');
113
+ return (item.hasAttribute('download') ||
114
+ (target !== null && target.trim() !== '' && target.trim().toLowerCase() !== '_self'));
115
+ }
116
+ function canCloseAfterItemActivation(item, event) {
117
+ return (isCollapsible &&
118
+ isMobileLayout &&
119
+ mobileMenuOpen &&
120
+ isEnabledNavigationItem(item) &&
121
+ !isModifiedClick(event) &&
122
+ !opensOutsideCurrentPage(item));
123
+ }
124
+ function moveFocusBeforeClosingItemsRegion() {
125
+ if (!browser || !itemsRegionElement)
126
+ return;
127
+ const activeElement = document.activeElement;
128
+ if (activeElement instanceof Element && itemsRegionElement.contains(activeElement)) {
129
+ focusMenuToggle();
130
+ }
131
+ }
132
+ function focusMenuToggle() {
133
+ const focusTarget = toggleElement ?? navigationBarElement?.querySelector(toggleFocusSelector);
134
+ focusTarget?.focus();
135
+ }
136
+ function handleClick(event) {
137
+ if (consumerOnClick) {
138
+ const currentTarget = event.currentTarget instanceof HTMLElement ? event.currentTarget : navigationBarElement;
139
+ consumerOnClick.call(currentTarget, event);
140
+ }
141
+ if (event.defaultPrevented)
142
+ return;
143
+ const navigationItem = getEventNavigationItem(event);
144
+ if (!navigationItem || !canCloseAfterItemActivation(navigationItem, event))
145
+ return;
146
+ moveFocusBeforeClosingItemsRegion();
147
+ mobileMenuOpen = false;
148
+ }
107
149
  function focusAdjacentNavigationItem(currentItem, direction) {
108
150
  const items = getNavigationItems();
109
151
  if (items.length === 0)
@@ -129,12 +171,11 @@ function handleKeyDown(event) {
129
171
  return;
130
172
  if (event.key === 'Escape' && isCollapsible && isMobileLayout && mobileMenuOpen) {
131
173
  mobileMenuOpen = false;
132
- // Return focus synchronously — toggleElement is captured on each click.
133
- toggleElement?.focus();
174
+ focusMenuToggle();
134
175
  return;
135
176
  }
136
177
  const navigationItem = getEventNavigationItem(event);
137
- if (!navigationItem)
178
+ if (!navigationItem || navigationItem !== event.target)
138
179
  return;
139
180
  if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
140
181
  event.preventDefault();
@@ -159,6 +200,7 @@ function handleKeyDown(event) {
159
200
  data-cinder-placement={placement}
160
201
  data-cinder-label-visibility={showLabels}
161
202
  data-cinder-menu-toggle-placement={menuTogglePlacement}
203
+ onclick={handleClick}
162
204
  onkeydown={handleKeyDown}
163
205
  >
164
206
  {#if isCollapsible && menuToggle && menuTogglePlacement === 'before-brand'}
@@ -40,6 +40,10 @@
40
40
  border-inline-end: none;
41
41
  }
42
42
 
43
+ .cinder-sidebar--desktop[data-cinder-ssr-mobile-fallback] {
44
+ display: none;
45
+ }
46
+
43
47
  .cinder-sidebar--desktop[data-cinder-collapsed] {
44
48
  display: none;
45
49
  }
@@ -24,9 +24,11 @@ const navigationLabel = $derived(`${validatedLabel} navigation`);
24
24
  // on Firefox and Safari.
25
25
  // Keep the fallback explicit for SSR-contract test environments that resolve
26
26
  // the client MediaQuery build while `window.matchMedia` is unavailable.
27
- const mobile = typeof window === 'undefined' || typeof window.matchMedia !== 'function'
28
- ? { current: false }
29
- : new MediaQuery('(max-width: 47.99rem)', false);
27
+ const hasMatchMedia = typeof window !== 'undefined' && typeof window.matchMedia === 'function';
28
+ const usesSsrResponsiveFallback = !hasMatchMedia;
29
+ const mobile = hasMatchMedia
30
+ ? new MediaQuery('(max-width: 47.99rem)', false)
31
+ : { current: false };
30
32
  const context = {
31
33
  get collapsed() {
32
34
  return collapsed;
@@ -84,6 +86,7 @@ setSidebarContext(context);
84
86
  class={classNames('cinder-sidebar', 'cinder-sidebar--desktop', className)}
85
87
  aria-label={validatedLabel}
86
88
  data-cinder-collapsed={collapsed ? '' : undefined}
89
+ data-cinder-ssr-mobile-fallback={usesSsrResponsiveFallback ? '' : undefined}
87
90
  >
88
91
  {@render sidebarContents(false)}
89
92
  </aside>