@tylertech/forge-ai 0.15.1 → 0.15.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.
@@ -11767,13 +11767,24 @@
11767
11767
  "kind": "field",
11768
11768
  "name": "resizable",
11769
11769
  "type": {
11770
- "text": "boolean"
11770
+ "text": "FeatureToggle"
11771
11771
  },
11772
11772
  "privacy": "public",
11773
- "default": "true",
11774
- "description": "Enables sidebar resizing.",
11773
+ "default": "'on'",
11774
+ "description": "Enables sidebar resizing. Set to `'off'` to disable and reset to the default width.",
11775
11775
  "attribute": "resizable"
11776
11776
  },
11777
+ {
11778
+ "kind": "field",
11779
+ "name": "width",
11780
+ "privacy": "public",
11781
+ "default": "420",
11782
+ "description": "The current width of the sidebar in pixels. Clamped to the min/max bounds and viewport.\nResized widths are persisted per tab in sessionStorage and shared across tabs via localStorage.\nOn load the session value is preferred, then the local value, then the default.",
11783
+ "attribute": "width",
11784
+ "type": {
11785
+ "text": "number"
11786
+ }
11787
+ },
11777
11788
  {
11778
11789
  "kind": "method",
11779
11790
  "name": "show",
@@ -11816,6 +11827,10 @@
11816
11827
  {
11817
11828
  "description": "Fired when the sidebar is closed",
11818
11829
  "name": "forge-ai-sidebar-close"
11830
+ },
11831
+ {
11832
+ "description": "Fired when a resize is committed via pointer release or keyboard step",
11833
+ "name": "forge-ai-sidebar-resize"
11819
11834
  }
11820
11835
  ],
11821
11836
  "attributes": [
@@ -11831,11 +11846,17 @@
11831
11846
  {
11832
11847
  "name": "resizable",
11833
11848
  "type": {
11834
- "text": "boolean"
11849
+ "text": "FeatureToggle"
11835
11850
  },
11836
- "default": "true",
11837
- "description": "Enables sidebar resizing.",
11851
+ "default": "'on'",
11852
+ "description": "Enables sidebar resizing. Set to `'off'` to disable and reset to the default width.",
11838
11853
  "fieldName": "resizable"
11854
+ },
11855
+ {
11856
+ "name": "width",
11857
+ "default": "DEFAULT_WIDTH",
11858
+ "description": "The current width of the sidebar in pixels. Clamped to the min/max bounds and viewport.\nResized widths are persisted per tab in sessionStorage and shared across tabs via localStorage.\nOn load the session value is preferred, then the local value, then the default.",
11859
+ "fieldName": "width"
11839
11860
  }
11840
11861
  ],
11841
11862
  "superclass": {
@@ -11919,12 +11940,23 @@
11919
11940
  "kind": "field",
11920
11941
  "name": "resizable",
11921
11942
  "type": {
11922
- "text": "boolean"
11943
+ "text": "FeatureToggle"
11923
11944
  },
11924
11945
  "privacy": "public",
11925
- "default": "true",
11946
+ "default": "'on'",
11947
+ "description": "Enables sidebar resizing. Set to `'off'` to disable.",
11926
11948
  "attribute": "resizable"
11927
11949
  },
11950
+ {
11951
+ "kind": "field",
11952
+ "name": "width",
11953
+ "type": {
11954
+ "text": "number | undefined"
11955
+ },
11956
+ "privacy": "public",
11957
+ "description": "The current width of the sidebar in pixels. Retained across expand/collapse.",
11958
+ "attribute": "width"
11959
+ },
11928
11960
  {
11929
11961
  "kind": "method",
11930
11962
  "name": "show",
@@ -11992,6 +12024,10 @@
11992
12024
  {
11993
12025
  "description": "Fired when the sidebar chat is collapsed from modal",
11994
12026
  "name": "forge-ai-sidebar-chat-collapse"
12027
+ },
12028
+ {
12029
+ "description": "Fired when the sidebar width is resized",
12030
+ "name": "forge-ai-sidebar-chat-resize"
11995
12031
  }
11996
12032
  ],
11997
12033
  "attributes": [
@@ -12014,10 +12050,19 @@
12014
12050
  {
12015
12051
  "name": "resizable",
12016
12052
  "type": {
12017
- "text": "boolean"
12053
+ "text": "FeatureToggle"
12018
12054
  },
12019
- "default": "true",
12055
+ "default": "'on'",
12056
+ "description": "Enables sidebar resizing. Set to `'off'` to disable.",
12020
12057
  "fieldName": "resizable"
12058
+ },
12059
+ {
12060
+ "name": "width",
12061
+ "type": {
12062
+ "text": "number | undefined"
12063
+ },
12064
+ "description": "The current width of the sidebar in pixels. Retained across expand/collapse.",
12065
+ "fieldName": "width"
12021
12066
  }
12022
12067
  ],
12023
12068
  "superclass": {
@@ -16331,13 +16376,21 @@
16331
16376
  "path": "src/lib/ai-response-message-toolbar/ai-response-message-toolbar.ts",
16332
16377
  "lineNumber": 43
16333
16378
  },
16379
+ "ForgeAiSidebarResizeEventData": {
16380
+ "path": "src/lib/ai-sidebar/ai-sidebar.ts",
16381
+ "lineNumber": 33
16382
+ },
16334
16383
  "AiSidebarComponent": {
16335
16384
  "path": "src/lib/ai-sidebar/ai-sidebar.ts",
16336
- "lineNumber": 36
16385
+ "lineNumber": 48
16386
+ },
16387
+ "ForgeAiSidebarChatResizeEventData": {
16388
+ "path": "src/lib/ai-sidebar-chat/ai-sidebar-chat.ts",
16389
+ "lineNumber": 26
16337
16390
  },
16338
16391
  "AiSidebarChatComponent": {
16339
16392
  "path": "src/lib/ai-sidebar-chat/ai-sidebar-chat.ts",
16340
- "lineNumber": 42
16393
+ "lineNumber": 48
16341
16394
  },
16342
16395
  "AiSlashCommandMenuComponent": {
16343
16396
  "path": "src/lib/ai-slash-command-menu/ai-slash-command-menu.ts",
@@ -16517,7 +16570,7 @@
16517
16570
  },
16518
16571
  "ResizeController": {
16519
16572
  "path": "src/lib/core/resize-controller.ts",
16520
- "lineNumber": 18
16573
+ "lineNumber": 19
16521
16574
  },
16522
16575
  "ThoughtBaseComponent": {
16523
16576
  "path": "src/lib/ai-chain-of-thought/thought-base/thought-base.ts",
@@ -40,7 +40,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
40
40
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
41
41
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
42
42
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
43
- var _headerRef, _gradientContainerRef, _historyButtonRef, _historyPopoverSearchRef, _historyViewSearchRef, _internals, _pendingThreadDeleteSource, _deleteThreadController, _AiChatbotLauncherComponent_instances, setViewState_fn, focusAfterUpdate_fn, transitionToConversation_fn, commitConversationTransition_fn, transitionToWelcome_fn, transitionToHistory_fn, handleHistoryBack_fn, handleHeaderClear_fn, handleHeaderInfo_fn, handleThreadRename_fn, handleEditSave_fn, handleEditCancel_fn, handleThreadNameClick_fn, handleThreadDelete_fn, handleHistoryButtonClick_fn, handleHistoryPopoverToggle_fn, handleViewAllClick_fn, handleThreadsSearchQuery_fn, handleThreadsSearchLoadMore_fn, handleThreadsSearchSelect_fn, handleThreadsSearchRetry_fn, handleThreadsSearchNewChat_fn, handleThreadsSearchRename_fn, handleThreadsSearchDelete_fn, handleThreadsSearchDeleteConfirm_fn, headingElement_get, promptTemplate_get, welcomeHeaderTemplate_get, welcomeSuggestionsTemplate_get, threadName_get, threadActionsMenu_get, conversationContentTemplate_get, historyContentTemplate_get, mainContentTemplate_get, showThreadsLoading_get, historyButtonTemplate_get, historyPopoverTemplate_get, promptSectionTemplate_get;
43
+ var _headerRef, _gradientContainerRef, _historyButtonRef, _historyPopoverSearchRef, _historyViewSearchRef, _internals, _pendingThreadDeleteSource, _deleteThreadController, _AiChatbotLauncherComponent_instances, setViewState_fn, focusAfterUpdate_fn, transitionToConversation_fn, commitConversationTransition_fn, transitionToWelcome_fn, transitionToHistory_fn, handleHistoryBack_fn, handleHeaderClear_fn, handleHeaderInfo_fn, handleThreadRename_fn, handleEditSave_fn, handleEditCancel_fn, handleThreadNameClick_fn, handleThreadDelete_fn, handleHistoryButtonClick_fn, handleHistoryPopoverToggle_fn, handleHistoryPopoverKeydown_fn, handleViewAllClick_fn, handleThreadsSearchQuery_fn, handleThreadsSearchLoadMore_fn, handleThreadsSearchSelect_fn, handleThreadsSearchRetry_fn, handleThreadsSearchNewChat_fn, handleThreadsSearchRename_fn, handleThreadsSearchDelete_fn, handleThreadsSearchDeleteConfirm_fn, headingElement_get, promptTemplate_get, welcomeHeaderTemplate_get, welcomeSuggestionsTemplate_get, threadName_get, threadActionsMenu_get, conversationContentTemplate_get, historyContentTemplate_get, mainContentTemplate_get, showThreadsLoading_get, historyButtonTemplate_get, historyPopoverTemplate_get, promptSectionTemplate_get;
44
44
  const HISTORY_POPOVER_INSET = 48;
45
45
  const AiChatbotLauncherComponentTagName = "forge-ai-chatbot-launcher";
46
46
  let AiChatbotLauncherComponent = class extends AiChatbotBase {
@@ -315,11 +315,22 @@ handleHistoryPopoverToggle_fn = function(evt) {
315
315
  void __privateMethod(this, _AiChatbotLauncherComponent_instances, focusAfterUpdate_fn).call(this, () => __privateGet(this, _historyPopoverSearchRef).value);
316
316
  return;
317
317
  }
318
+ const activeElement = this.shadowRoot?.activeElement ?? null;
319
+ const focusInsidePopover = evt.currentTarget.contains(activeElement);
318
320
  const focusLost = this.ownerDocument.activeElement === this.ownerDocument.body;
319
- if (focusLost && this._viewState !== "history") {
321
+ if ((focusInsidePopover || focusLost) && this._viewState !== "history") {
320
322
  __privateGet(this, _historyButtonRef).value?.focus();
321
323
  }
322
324
  };
325
+ handleHistoryPopoverKeydown_fn = function(evt) {
326
+ if (evt.key !== "Escape" || evt.defaultPrevented) {
327
+ return;
328
+ }
329
+ evt.preventDefault();
330
+ evt.stopPropagation();
331
+ this._historyPopoverOpen = false;
332
+ void __privateMethod(this, _AiChatbotLauncherComponent_instances, focusAfterUpdate_fn).call(this, () => __privateGet(this, _historyButtonRef).value);
333
+ };
323
334
  handleViewAllClick_fn = function() {
324
335
  this._historyPopoverOpen = false;
325
336
  __privateMethod(this, _AiChatbotLauncherComponent_instances, transitionToHistory_fn).call(this);
@@ -659,6 +670,7 @@ historyPopoverTemplate_get = function() {
659
670
  .flip=${true}
660
671
  .autoSize=${true}
661
672
  ?open=${this._historyPopoverOpen}
673
+ @keydown=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleHistoryPopoverKeydown_fn)}
662
674
  @forge-ai-popover-toggle=${__privateMethod(this, _AiChatbotLauncherComponent_instances, handleHistoryPopoverToggle_fn)}>
663
675
  <div class="history-popover" style=${popoverWidth ? `width: ${popoverWidth}px` : ""}>
664
676
  <forge-ai-threads-search
@@ -1,4 +1,5 @@
1
- import { LitElement, TemplateResult, CSSResult } from 'lit';
1
+ import { LitElement, PropertyValues, TemplateResult, CSSResult } from 'lit';
2
+ import { FeatureToggle } from '../ai-chatbot';
2
3
  declare global {
3
4
  interface HTMLElementTagNameMap {
4
5
  'forge-ai-sidebar': AiSidebarComponent;
@@ -6,8 +7,12 @@ declare global {
6
7
  interface HTMLElementEventMap {
7
8
  'forge-ai-sidebar-open': CustomEvent<void>;
8
9
  'forge-ai-sidebar-close': CustomEvent<void>;
10
+ 'forge-ai-sidebar-resize': CustomEvent<ForgeAiSidebarResizeEventData>;
9
11
  }
10
12
  }
13
+ export interface ForgeAiSidebarResizeEventData {
14
+ width: number;
15
+ }
11
16
  export declare const AiSidebarComponentTagName: keyof HTMLElementTagNameMap;
12
17
  /**
13
18
  * @tag forge-ai-sidebar
@@ -16,6 +21,7 @@ export declare const AiSidebarComponentTagName: keyof HTMLElementTagNameMap;
16
21
  *
17
22
  * @fires forge-ai-sidebar-open - Fired when the sidebar is opened
18
23
  * @fires forge-ai-sidebar-close - Fired when the sidebar is closed
24
+ * @fires forge-ai-sidebar-resize - Fired when a resize is committed via pointer release or keyboard step
19
25
  */
20
26
  export declare class AiSidebarComponent extends LitElement {
21
27
  #private;
@@ -25,10 +31,15 @@ export declare class AiSidebarComponent extends LitElement {
25
31
  */
26
32
  open: boolean;
27
33
  /**
28
- * Enables sidebar resizing.
34
+ * Enables sidebar resizing. Set to `'off'` to disable and reset to the default width.
35
+ */
36
+ resizable: FeatureToggle;
37
+ /**
38
+ * The current width of the sidebar in pixels. Clamped to the min/max bounds and viewport.
39
+ * Resized widths are persisted per tab in sessionStorage and shared across tabs via localStorage.
40
+ * On load the session value is preferred, then the local value, then the default.
29
41
  */
30
- resizable: boolean;
31
- private _width;
42
+ width: number;
32
43
  /**
33
44
  * Indicates whether the sidebar is in a closing animation state.
34
45
  */
@@ -41,6 +52,7 @@ export declare class AiSidebarComponent extends LitElement {
41
52
  * Indicates whether the sidebar is being resized.
42
53
  */
43
54
  private _isResizing;
55
+ constructor();
44
56
  render(): TemplateResult;
45
57
  /**
46
58
  * Opens the sidebar.
@@ -54,6 +66,5 @@ export declare class AiSidebarComponent extends LitElement {
54
66
  * Toggles the sidebar open state.
55
67
  */
56
68
  toggle(): void;
57
- firstUpdated(): void;
58
- updated(changedProperties: Map<string, unknown>): void;
69
+ willUpdate(changedProperties: PropertyValues<this>): void;
59
70
  }
@@ -5,6 +5,7 @@ import { styleMap } from "lit/directives/style-map.js";
5
5
  import { createRef, ref } from "lit/directives/ref.js";
6
6
  import { when } from "lit/directives/when.js";
7
7
  import { ResizeController } from "../core/resize-controller.mjs";
8
+ import { readStoredNumber, writeStoredNumber, removeStoredValue } from "../utils/storage-utils.mjs";
8
9
  import styles from "./ai-sidebar.scss.mjs";
9
10
  var __defProp = Object.defineProperty;
10
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -24,18 +25,20 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
24
25
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
25
26
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
26
27
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
27
- var _resizeHandleRef, _drawerRef, _containerRef, _resizeController, _AiSidebarComponent_instances, handleContainerTransitionEnd_fn, handleDrawerTransitionEnd_fn, handleResizePointerDown_fn, handleResizeKeyDown_fn, dispatchEvent_fn;
28
+ var _resizeHandleRef, _drawerRef, _containerRef, _resizeController, _AiSidebarComponent_instances, handleContainerTransitionEnd_fn, handleDrawerTransitionEnd_fn, handleResizePointerDown_fn, handleResizeKeyDown_fn, handleResizeCommit_fn, readPersistedWidth_fn, persistWidth_fn, clearPersistedWidth_fn, dispatchEvent_fn;
28
29
  const DEFAULT_WIDTH = 420;
29
30
  const MIN_WIDTH = 360;
30
31
  const MAX_WIDTH = 800;
32
+ const WIDTH_STORAGE_KEY = "forge-ai-sidebar-width";
33
+ const WIDTH_STORAGE_KINDS = ["session", "local"];
31
34
  const AiSidebarComponentTagName = "forge-ai-sidebar";
32
35
  let AiSidebarComponent = class extends LitElement {
33
36
  constructor() {
34
- super(...arguments);
37
+ super();
35
38
  __privateAdd(this, _AiSidebarComponent_instances);
36
39
  this.open = false;
37
- this.resizable = true;
38
- this._width = DEFAULT_WIDTH;
40
+ this.resizable = "on";
41
+ this.width = DEFAULT_WIDTH;
39
42
  this._closing = false;
40
43
  this._containerClosing = false;
41
44
  this._isResizing = false;
@@ -43,6 +46,26 @@ let AiSidebarComponent = class extends LitElement {
43
46
  __privateAdd(this, _drawerRef, createRef());
44
47
  __privateAdd(this, _containerRef, createRef());
45
48
  __privateAdd(this, _resizeController);
49
+ this.width = __privateMethod(this, _AiSidebarComponent_instances, readPersistedWidth_fn).call(this) ?? DEFAULT_WIDTH;
50
+ __privateSet(this, _resizeController, new ResizeController(this, {
51
+ targetElementRef: __privateGet(this, _containerRef),
52
+ resizeHandleRef: __privateGet(this, _resizeHandleRef),
53
+ minWidth: MIN_WIDTH,
54
+ maxWidth: MAX_WIDTH,
55
+ defaultWidth: DEFAULT_WIDTH,
56
+ onResize: (width) => {
57
+ this.width = width;
58
+ },
59
+ onResizeStart: () => {
60
+ this._isResizing = true;
61
+ },
62
+ onResizeEnd: () => {
63
+ this._isResizing = false;
64
+ },
65
+ onCommit: (width) => {
66
+ __privateMethod(this, _AiSidebarComponent_instances, handleResizeCommit_fn).call(this, width);
67
+ }
68
+ }));
46
69
  }
47
70
  render() {
48
71
  const containerClasses = {
@@ -64,7 +87,7 @@ let AiSidebarComponent = class extends LitElement {
64
87
  "resize-handle--active": this._isResizing
65
88
  };
66
89
  const containerStyles = {
67
- "--forge-drawer-width": `${this._width}px`
90
+ "--forge-drawer-width": `${this.width}px`
68
91
  };
69
92
  return html`
70
93
  <div
@@ -80,7 +103,7 @@ let AiSidebarComponent = class extends LitElement {
80
103
  aria-hidden=${!this.open}
81
104
  @transitionend=${__privateMethod(this, _AiSidebarComponent_instances, handleDrawerTransitionEnd_fn)}>
82
105
  ${when(
83
- this.resizable,
106
+ this.resizable === "on",
84
107
  () => html`
85
108
  <div
86
109
  ${ref(__privateGet(this, _resizeHandleRef))}
@@ -90,11 +113,18 @@ let AiSidebarComponent = class extends LitElement {
90
113
  aria-label="Resize chatbot panel, use left and right arrow keys"
91
114
  aria-valuemin="${MIN_WIDTH}"
92
115
  aria-valuemax="${MAX_WIDTH}"
93
- aria-valuenow=${this._width}
94
- aria-valuetext="${this._width} pixels"
116
+ aria-valuenow=${this.width}
117
+ aria-valuetext="${this.width} pixels"
95
118
  tabindex="0"
96
119
  @pointerdown=${__privateMethod(this, _AiSidebarComponent_instances, handleResizePointerDown_fn)}
97
- @keydown=${__privateMethod(this, _AiSidebarComponent_instances, handleResizeKeyDown_fn)}></div>
120
+ @keydown=${__privateMethod(this, _AiSidebarComponent_instances, handleResizeKeyDown_fn)}>
121
+ <span class="resize-handle__grip" aria-hidden="true">
122
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
123
+ <path
124
+ d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2m0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2m0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2" />
125
+ </svg>
126
+ </span>
127
+ </div>
98
128
  `
99
129
  )}
100
130
  <slot></slot>
@@ -132,27 +162,15 @@ let AiSidebarComponent = class extends LitElement {
132
162
  this.show();
133
163
  }
134
164
  }
135
- firstUpdated() {
136
- __privateSet(this, _resizeController, new ResizeController(this, {
137
- targetElementRef: __privateGet(this, _containerRef),
138
- resizeHandleRef: __privateGet(this, _resizeHandleRef),
139
- minWidth: MIN_WIDTH,
140
- maxWidth: MAX_WIDTH,
141
- defaultWidth: DEFAULT_WIDTH,
142
- onResize: (width) => {
143
- this._width = width;
144
- },
145
- onResizeStart: () => {
146
- this._isResizing = true;
147
- },
148
- onResizeEnd: () => {
149
- this._isResizing = false;
165
+ willUpdate(changedProperties) {
166
+ if (changedProperties.has("width") && this.width !== __privateGet(this, _resizeController).currentWidth) {
167
+ __privateGet(this, _resizeController).setWidth(this.width);
168
+ }
169
+ if (changedProperties.has("resizable") && this.resizable === "off") {
170
+ __privateGet(this, _resizeController).resetWidth();
171
+ if (this.hasUpdated) {
172
+ __privateMethod(this, _AiSidebarComponent_instances, clearPersistedWidth_fn).call(this);
150
173
  }
151
- }));
152
- }
153
- updated(changedProperties) {
154
- if (changedProperties.has("resizable") && !this.resizable) {
155
- this._width = DEFAULT_WIDTH;
156
174
  }
157
175
  }
158
176
  };
@@ -174,15 +192,39 @@ handleDrawerTransitionEnd_fn = function(evt) {
174
192
  }
175
193
  };
176
194
  handleResizePointerDown_fn = function(event) {
177
- __privateGet(this, _resizeController)?.handlePointerDown(event);
195
+ __privateGet(this, _resizeController).handlePointerDown(event);
178
196
  };
179
197
  handleResizeKeyDown_fn = function(event) {
180
- __privateGet(this, _resizeController)?.handleKeyDown(event);
198
+ __privateGet(this, _resizeController).handleKeyDown(event);
181
199
  };
182
- dispatchEvent_fn = function(type) {
200
+ handleResizeCommit_fn = function(width) {
201
+ __privateMethod(this, _AiSidebarComponent_instances, dispatchEvent_fn).call(this, "forge-ai-sidebar-resize", { width });
202
+ __privateMethod(this, _AiSidebarComponent_instances, persistWidth_fn).call(this, width);
203
+ };
204
+ readPersistedWidth_fn = function() {
205
+ for (const kind of WIDTH_STORAGE_KINDS) {
206
+ const width = readStoredNumber({ kind, key: WIDTH_STORAGE_KEY });
207
+ if (width !== void 0) {
208
+ return width;
209
+ }
210
+ }
211
+ return void 0;
212
+ };
213
+ persistWidth_fn = function(width) {
214
+ for (const kind of WIDTH_STORAGE_KINDS) {
215
+ writeStoredNumber({ kind, key: WIDTH_STORAGE_KEY, value: width });
216
+ }
217
+ };
218
+ clearPersistedWidth_fn = function() {
219
+ for (const kind of WIDTH_STORAGE_KINDS) {
220
+ removeStoredValue({ kind, key: WIDTH_STORAGE_KEY });
221
+ }
222
+ };
223
+ dispatchEvent_fn = function(type, detail) {
183
224
  const event = new CustomEvent(type, {
184
225
  bubbles: true,
185
- composed: true
226
+ composed: true,
227
+ detail
186
228
  });
187
229
  this.dispatchEvent(event);
188
230
  };
@@ -191,11 +233,11 @@ __decorateClass([
191
233
  property({ type: Boolean })
192
234
  ], AiSidebarComponent.prototype, "open", 2);
193
235
  __decorateClass([
194
- property({ type: Boolean })
236
+ property()
195
237
  ], AiSidebarComponent.prototype, "resizable", 2);
196
238
  __decorateClass([
197
- state()
198
- ], AiSidebarComponent.prototype, "_width", 2);
239
+ property({ type: Number })
240
+ ], AiSidebarComponent.prototype, "width", 2);
199
241
  __decorateClass([
200
242
  state()
201
243
  ], AiSidebarComponent.prototype, "_closing", 2);
@@ -1,4 +1,4 @@
1
- const styles = '/**\n * @license\n * Copyright Tyler Technologies, Inc.\n * License: Apache-2.0\n */\n.forge-drawer {\n --_drawer-background:var(--forge-drawer-background, var(--forge-theme-surface, #ffffff));\n --_drawer-border-color:var(--forge-drawer-border-color, var(--forge-theme-outline, #e0e0e0));\n --_drawer-width:var(--forge-drawer-width, 256px);\n --_drawer-border-width:var(--forge-drawer-border-width, var(--forge-border-thin, 1px));\n --_drawer-transition-duration:var(--forge-drawer-transition-duration, var(--forge-animation-duration-medium1, 250ms));\n --_drawer-transition-easing:var(--forge-drawer-transition-easing, var(--forge-animation-easing-standard, cubic-bezier(0.2, 0, 0, 1)));\n --_drawer-transition-duration-close:var(--forge-drawer-transition-duration-close, var(--forge-animation-duration-short4, 200ms));\n}\n\n.forge-drawer {\n width: var(--_drawer-width);\n height: 100%;\n box-sizing: border-box;\n overflow: hidden;\n display: grid;\n grid-template-columns: 1fr;\n grid-template-rows: auto 1fr auto;\n background-color: var(--_drawer-background);\n border-color: var(--_drawer-border-color);\n transition-property: transform;\n transition-duration: var(--_drawer-transition-duration);\n transition-timing-function: var(--_drawer-transition-easing);\n border-right-width: var(--_drawer-border-width);\n border-right-style: solid;\n}\n\n.forge-drawer--closing {\n transform: translateX(-100%);\n left: 0;\n z-index: var(--forge-z-index-surface, 1);\n position: absolute;\n top: 0;\n right: 0;\n transition-duration: var(--_drawer-transition-duration-close);\n}\n\n.forge-drawer--closed {\n transform: translateX(-100%);\n left: 0;\n width: 0;\n border: none;\n}\n\n.forge-drawer--right {\n border-left-width: var(--_drawer-border-width);\n border-left-style: solid;\n border-right: none;\n}\n\n.forge-drawer--right.forge-drawer--closing {\n transform: translateX(100%);\n right: 0;\n left: auto;\n z-index: var(--forge-z-index-surface, 1);\n position: absolute;\n top: 0;\n right: 0;\n transition-duration: var(--_drawer-transition-duration-close);\n}\n\n.forge-drawer--right.forge-drawer--closed {\n transform: translateX(100%);\n right: 0;\n left: auto;\n width: 0;\n border: none;\n}\n\n.forge-drawer--mini {\n width: var(--_mini-drawer-width);\n z-index: var(--forge-z-index-surface, 1);\n transition: width var(--_mini-drawer-transition-duration) var(--_mini-drawer-transition-easing);\n}\n\n:host {\n display: block;\n width: auto;\n height: 100%;\n overflow: hidden;\n}\n\n.animation-container {\n --forge-drawer-width: 420px;\n width: var(--forge-drawer-width);\n height: 100%;\n position: relative;\n transition: width 250ms cubic-bezier(0.2, 0, 0, 1);\n overflow: hidden;\n}\n.animation-container--closed {\n width: 0;\n}\n.animation-container--closing {\n width: 0;\n}\n.animation-container--open {\n width: var(--forge-drawer-width);\n}\n.animation-container--resizing {\n transition: none;\n}\n\n.forge-drawer {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n width: var(--forge-drawer-width);\n height: 100%;\n position: absolute;\n top: 0;\n right: 0;\n}\n.forge-drawer--resizing {\n user-select: none;\n transition: none;\n}\n\n.resize-handle {\n position: absolute;\n top: 0;\n left: 0;\n width: 8px;\n height: 100%;\n cursor: ew-resize;\n z-index: 10;\n}\n.resize-handle::before {\n content: "";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 1px;\n background-color: var(--forge-theme-outline, #e0e0e0);\n opacity: 0;\n transition: all var(--forge-animation-duration-short4, 200ms) var(--forge-animation-easing-standard, cubic-bezier(0.2, 0, 0, 1));\n}\n.resize-handle:hover::before {\n opacity: 0.4;\n width: 2px;\n background-color: var(--forge-theme-primary, #3f51b5);\n}\n.resize-handle:focus-visible {\n outline: 2px solid var(--forge-theme-primary, #3f51b5);\n outline-offset: -2px;\n}\n.resize-handle:focus-visible::before {\n opacity: 0.6;\n width: 2px;\n background-color: var(--forge-theme-primary, #3f51b5);\n}\n.resize-handle--active::before {\n opacity: 0.8;\n width: 3px;\n background-color: var(--forge-theme-primary, #3f51b5);\n}\n\n:host([resizable=false]) .resize-handle {\n display: none;\n}';
1
+ const styles = '/**\n * @license\n * Copyright Tyler Technologies, Inc.\n * License: Apache-2.0\n */\n.forge-drawer {\n --_drawer-background:var(--forge-drawer-background, var(--forge-theme-surface, #ffffff));\n --_drawer-border-color:var(--forge-drawer-border-color, var(--forge-theme-outline, #e0e0e0));\n --_drawer-width:var(--forge-drawer-width, 256px);\n --_drawer-border-width:var(--forge-drawer-border-width, var(--forge-border-thin, 1px));\n --_drawer-transition-duration:var(--forge-drawer-transition-duration, var(--forge-animation-duration-medium1, 250ms));\n --_drawer-transition-easing:var(--forge-drawer-transition-easing, var(--forge-animation-easing-standard, cubic-bezier(0.2, 0, 0, 1)));\n --_drawer-transition-duration-close:var(--forge-drawer-transition-duration-close, var(--forge-animation-duration-short4, 200ms));\n}\n\n.forge-drawer {\n width: var(--_drawer-width);\n height: 100%;\n box-sizing: border-box;\n overflow: hidden;\n display: grid;\n grid-template-columns: 1fr;\n grid-template-rows: auto 1fr auto;\n background-color: var(--_drawer-background);\n border-color: var(--_drawer-border-color);\n transition-property: transform;\n transition-duration: var(--_drawer-transition-duration);\n transition-timing-function: var(--_drawer-transition-easing);\n border-right-width: var(--_drawer-border-width);\n border-right-style: solid;\n}\n\n.forge-drawer--closing {\n transform: translateX(-100%);\n left: 0;\n z-index: var(--forge-z-index-surface, 1);\n position: absolute;\n top: 0;\n right: 0;\n transition-duration: var(--_drawer-transition-duration-close);\n}\n\n.forge-drawer--closed {\n transform: translateX(-100%);\n left: 0;\n width: 0;\n border: none;\n}\n\n.forge-drawer--right {\n border-left-width: var(--_drawer-border-width);\n border-left-style: solid;\n border-right: none;\n}\n\n.forge-drawer--right.forge-drawer--closing {\n transform: translateX(100%);\n right: 0;\n left: auto;\n z-index: var(--forge-z-index-surface, 1);\n position: absolute;\n top: 0;\n right: 0;\n transition-duration: var(--_drawer-transition-duration-close);\n}\n\n.forge-drawer--right.forge-drawer--closed {\n transform: translateX(100%);\n right: 0;\n left: auto;\n width: 0;\n border: none;\n}\n\n.forge-drawer--mini {\n width: var(--_mini-drawer-width);\n z-index: var(--forge-z-index-surface, 1);\n transition: width var(--_mini-drawer-transition-duration) var(--_mini-drawer-transition-easing);\n}\n\n:host {\n display: block;\n width: auto;\n height: 100%;\n overflow: hidden;\n}\n\n.animation-container {\n --forge-drawer-width: 420px;\n width: var(--forge-drawer-width);\n height: 100%;\n position: relative;\n transition: width 250ms cubic-bezier(0.2, 0, 0, 1);\n overflow: hidden;\n}\n.animation-container--closed {\n width: 0;\n}\n.animation-container--closing {\n width: 0;\n}\n.animation-container--open {\n width: var(--forge-drawer-width);\n}\n.animation-container--resizing {\n transition: none;\n}\n\n.forge-drawer {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n width: var(--forge-drawer-width);\n height: 100%;\n position: absolute;\n top: 0;\n right: 0;\n}\n.forge-drawer--resizing {\n user-select: none;\n transition: none;\n}\n\n.resize-handle {\n position: absolute;\n top: 0;\n left: 0;\n width: 8px;\n height: 100%;\n cursor: ew-resize;\n z-index: 10;\n}\n.resize-handle::before {\n content: "";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 1px;\n background-color: var(--forge-theme-outline, #e0e0e0);\n opacity: 0;\n transition: all var(--forge-animation-duration-short4, 200ms) var(--forge-animation-easing-standard, cubic-bezier(0.2, 0, 0, 1));\n}\n.resize-handle__grip {\n position: absolute;\n top: 50%;\n left: 0;\n translate: 0 -50%;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 6px;\n height: 32px;\n color: var(--forge-theme-text-low, rgba(0, 0, 0, 0.38));\n background-color: var(--forge-theme-surface, #ffffff);\n border: 1px solid var(--forge-theme-outline, #e0e0e0);\n border-inline-start: none;\n border-start-end-radius: calc(var(--forge-shape-medium, 4px) * var(--forge-shape-factor, 1));\n border-end-end-radius: calc(var(--forge-shape-medium, 4px) * var(--forge-shape-factor, 1));\n pointer-events: none;\n transition: color var(--forge-animation-duration-short4, 200ms) var(--forge-animation-easing-standard, cubic-bezier(0.2, 0, 0, 1)), border-color var(--forge-animation-duration-short4, 200ms) var(--forge-animation-easing-standard, cubic-bezier(0.2, 0, 0, 1));\n}\n.resize-handle__grip svg {\n flex-shrink: 0;\n width: 12px;\n height: 12px;\n fill: currentColor;\n}\n.resize-handle:hover::before {\n opacity: 0.4;\n width: 2px;\n background-color: var(--forge-theme-primary, #3f51b5);\n}\n.resize-handle:focus-visible {\n outline: 2px solid var(--forge-theme-primary, #3f51b5);\n outline-offset: -2px;\n}\n.resize-handle:focus-visible::before {\n opacity: 0.6;\n width: 2px;\n background-color: var(--forge-theme-primary, #3f51b5);\n}\n.resize-handle--active::before {\n opacity: 0.8;\n width: 3px;\n background-color: var(--forge-theme-primary, #3f51b5);\n}\n.resize-handle:hover .resize-handle__grip, .resize-handle:focus-visible .resize-handle__grip, .resize-handle--active .resize-handle__grip {\n color: var(--forge-theme-primary, #3f51b5);\n border-color: var(--forge-theme-primary, #3f51b5);\n}';
2
2
  export {
3
3
  styles as default
4
4
  };
@@ -1 +1,2 @@
1
1
  export { AiSidebarComponent, AiSidebarComponentTagName } from './ai-sidebar';
2
+ export type { ForgeAiSidebarResizeEventData } from './ai-sidebar';
@@ -1,4 +1,5 @@
1
1
  import { LitElement, TemplateResult, CSSResult } from 'lit';
2
+ import { FeatureToggle } from '../ai-chatbot';
2
3
  declare global {
3
4
  interface HTMLElementTagNameMap {
4
5
  'forge-ai-sidebar-chat': AiSidebarChatComponent;
@@ -8,8 +9,12 @@ declare global {
8
9
  'forge-ai-sidebar-chat-close': CustomEvent<void>;
9
10
  'forge-ai-sidebar-chat-expand': CustomEvent<void>;
10
11
  'forge-ai-sidebar-chat-collapse': CustomEvent<void>;
12
+ 'forge-ai-sidebar-chat-resize': CustomEvent<ForgeAiSidebarChatResizeEventData>;
11
13
  }
12
14
  }
15
+ export interface ForgeAiSidebarChatResizeEventData {
16
+ width: number;
17
+ }
13
18
  export declare const AiSidebarChatComponentTagName: keyof HTMLElementTagNameMap;
14
19
  /**
15
20
  * @tag forge-ai-sidebar-chat
@@ -20,6 +25,7 @@ export declare const AiSidebarChatComponentTagName: keyof HTMLElementTagNameMap;
20
25
  * @fires forge-ai-sidebar-chat-close - Fired when the sidebar chat is closed
21
26
  * @fires forge-ai-sidebar-chat-expand - Fired when the sidebar chat is expanded to modal
22
27
  * @fires forge-ai-sidebar-chat-collapse - Fired when the sidebar chat is collapsed from modal
28
+ * @fires forge-ai-sidebar-chat-resize - Fired when the sidebar width is resized
23
29
  *
24
30
  * @description A form factor component that positions a slotted chatbot in a sidebar or modal.
25
31
  * Manages positioning and expand/collapse state while delegating chat functionality to the slotted chatbot.
@@ -31,7 +37,14 @@ export declare class AiSidebarChatComponent extends LitElement {
31
37
  static styles: CSSResult;
32
38
  open: boolean;
33
39
  expanded: boolean;
34
- resizable: boolean;
40
+ /**
41
+ * Enables sidebar resizing. Set to `'off'` to disable.
42
+ */
43
+ resizable: FeatureToggle;
44
+ /**
45
+ * The current width of the sidebar in pixels. Retained across expand/collapse.
46
+ */
47
+ width?: number;
35
48
  render(): TemplateResult;
36
49
  show(): void;
37
50
  close(): void;
@@ -1,6 +1,6 @@
1
1
  import { unsafeCSS, LitElement, html } from "lit";
2
2
  import { property, customElement } from "lit/decorators.js";
3
- import { createRef, ref } from "lit/directives/ref.js";
3
+ import { ifDefined } from "lit/directives/if-defined.js";
4
4
  import { when } from "lit/directives/when.js";
5
5
  import "../ai-sidebar/ai-sidebar.mjs";
6
6
  import "../ai-modal/ai-modal.mjs";
@@ -22,7 +22,7 @@ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot
22
22
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
23
23
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
24
24
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
25
- var _sidebarRef, _modalRef, _slotContent, _AiSidebarChatComponent_instances, handleSidebarOpen_fn, handleSidebarClose_fn, handleFullscreenChange_fn, handleChatbotExpand_fn, handleChatbotMinimize_fn, handleModalClose_fn, dispatchEvent_fn;
25
+ var _slotContent, _AiSidebarChatComponent_instances, handleSidebarOpen_fn, handleSidebarClose_fn, handleSidebarResize_fn, handleFullscreenChange_fn, handleChatbotExpand_fn, handleChatbotMinimize_fn, handleModalClose_fn, dispatchEvent_fn;
26
26
  const AiSidebarChatComponentTagName = "forge-ai-sidebar-chat";
27
27
  let AiSidebarChatComponent = class extends LitElement {
28
28
  constructor() {
@@ -30,9 +30,7 @@ let AiSidebarChatComponent = class extends LitElement {
30
30
  __privateAdd(this, _AiSidebarChatComponent_instances);
31
31
  this.open = false;
32
32
  this.expanded = false;
33
- this.resizable = true;
34
- __privateAdd(this, _sidebarRef, createRef());
35
- __privateAdd(this, _modalRef, createRef());
33
+ this.resizable = "on";
36
34
  __privateAdd(this, _slotContent, html`
37
35
  <slot
38
36
  @forge-ai-chatbot-expand=${__privateMethod(this, _AiSidebarChatComponent_instances, handleChatbotExpand_fn)}
@@ -45,7 +43,6 @@ let AiSidebarChatComponent = class extends LitElement {
45
43
  this.expanded,
46
44
  () => html`
47
45
  <forge-ai-modal
48
- ${ref(__privateGet(this, _modalRef))}
49
46
  ?open=${this.open && this.expanded}
50
47
  @forge-ai-modal-fullscreen-change=${__privateMethod(this, _AiSidebarChatComponent_instances, handleFullscreenChange_fn)}
51
48
  @forge-ai-modal-close=${__privateMethod(this, _AiSidebarChatComponent_instances, handleModalClose_fn)}>
@@ -54,11 +51,12 @@ let AiSidebarChatComponent = class extends LitElement {
54
51
  `,
55
52
  () => html`
56
53
  <forge-ai-sidebar
57
- ${ref(__privateGet(this, _sidebarRef))}
58
54
  ?open=${this.open && !this.expanded}
59
- ?resizable=${this.resizable}
55
+ resizable=${this.resizable}
56
+ width=${ifDefined(this.width)}
60
57
  @forge-ai-sidebar-open=${__privateMethod(this, _AiSidebarChatComponent_instances, handleSidebarOpen_fn)}
61
- @forge-ai-sidebar-close=${__privateMethod(this, _AiSidebarChatComponent_instances, handleSidebarClose_fn)}>
58
+ @forge-ai-sidebar-close=${__privateMethod(this, _AiSidebarChatComponent_instances, handleSidebarClose_fn)}
59
+ @forge-ai-sidebar-resize=${__privateMethod(this, _AiSidebarChatComponent_instances, handleSidebarResize_fn)}>
62
60
  ${__privateGet(this, _slotContent)}
63
61
  </forge-ai-sidebar>
64
62
  `
@@ -97,8 +95,6 @@ let AiSidebarChatComponent = class extends LitElement {
97
95
  }
98
96
  }
99
97
  };
100
- _sidebarRef = /* @__PURE__ */ new WeakMap();
101
- _modalRef = /* @__PURE__ */ new WeakMap();
102
98
  _slotContent = /* @__PURE__ */ new WeakMap();
103
99
  _AiSidebarChatComponent_instances = /* @__PURE__ */ new WeakSet();
104
100
  handleSidebarOpen_fn = function() {
@@ -109,6 +105,10 @@ handleSidebarClose_fn = function() {
109
105
  this.open = false;
110
106
  __privateMethod(this, _AiSidebarChatComponent_instances, dispatchEvent_fn).call(this, "forge-ai-sidebar-chat-close");
111
107
  };
108
+ handleSidebarResize_fn = function(event) {
109
+ this.width = event.detail.width;
110
+ __privateMethod(this, _AiSidebarChatComponent_instances, dispatchEvent_fn).call(this, "forge-ai-sidebar-chat-resize", { width: this.width });
111
+ };
112
112
  handleFullscreenChange_fn = function(event) {
113
113
  const { isFullscreen } = event.detail;
114
114
  if (isFullscreen && !this.expanded) {
@@ -133,10 +133,11 @@ handleChatbotMinimize_fn = function() {
133
133
  handleModalClose_fn = function() {
134
134
  this.collapse();
135
135
  };
136
- dispatchEvent_fn = function(type) {
136
+ dispatchEvent_fn = function(type, detail) {
137
137
  const event = new CustomEvent(type, {
138
138
  bubbles: true,
139
- composed: true
139
+ composed: true,
140
+ detail
140
141
  });
141
142
  this.dispatchEvent(event);
142
143
  };
@@ -148,8 +149,11 @@ __decorateClass([
148
149
  property({ type: Boolean })
149
150
  ], AiSidebarChatComponent.prototype, "expanded", 2);
150
151
  __decorateClass([
151
- property({ type: Boolean })
152
+ property()
152
153
  ], AiSidebarChatComponent.prototype, "resizable", 2);
154
+ __decorateClass([
155
+ property({ type: Number })
156
+ ], AiSidebarChatComponent.prototype, "width", 2);
153
157
  AiSidebarChatComponent = __decorateClass([
154
158
  customElement(AiSidebarChatComponentTagName)
155
159
  ], AiSidebarChatComponent);
@@ -10,6 +10,7 @@ export interface ResizeControllerOptions {
10
10
  onResize?: (width: number) => void;
11
11
  onResizeStart?: () => void;
12
12
  onResizeEnd?: () => void;
13
+ onCommit?: (width: number) => void;
13
14
  }
14
15
  export declare class ResizeController implements ReactiveController {
15
16
  #private;
@@ -36,9 +36,10 @@ class ResizeController {
36
36
  return;
37
37
  }
38
38
  event.preventDefault();
39
- const newWidth = this.#currentWidth + delta;
40
- this.#currentWidth = this.#constrainWidth(newWidth);
39
+ const previousWidth = this.#currentWidth;
40
+ this.#currentWidth = this.#constrainWidth(previousWidth + delta);
41
41
  this.#applyWidth();
42
+ this.#commitIfChanged(previousWidth);
42
43
  };
43
44
  this.#handlePointerMove = (event) => {
44
45
  if (!this.#isResizing) {
@@ -62,6 +63,7 @@ class ResizeController {
62
63
  this.#isResizing = false;
63
64
  this.#host.requestUpdate();
64
65
  this.#options.onResizeEnd();
66
+ this.#commitIfChanged(this.#dragStartWidth);
65
67
  this.#removeResizeListeners();
66
68
  };
67
69
  this.#host = host;
@@ -76,6 +78,8 @@ class ResizeController {
76
78
  },
77
79
  onResizeEnd: () => {
78
80
  },
81
+ onCommit: () => {
82
+ },
79
83
  ...options
80
84
  };
81
85
  this.#currentWidth = this.#options.defaultWidth;
@@ -108,6 +112,11 @@ class ResizeController {
108
112
  }
109
113
  #handlePointerMove;
110
114
  #handlePointerUp;
115
+ #commitIfChanged(previousWidth) {
116
+ if (this.#currentWidth !== previousWidth) {
117
+ this.#options.onCommit(this.#currentWidth);
118
+ }
119
+ }
111
120
  #constrainWidth(width) {
112
121
  const availableWidth = window.innerWidth - RESIZE_SCREEN_BUFFER;
113
122
  const effectiveMaxWidth = Math.min(this.#options.maxWidth, availableWidth);
@@ -3,3 +3,4 @@ export { InfiniteScrollController } from './infinite-scroll-controller';
3
3
  export { KeyboardNavigationController } from './keyboard-navigation-controller';
4
4
  export { renderMarkdown, renderInlineMarkdown, type RenderMarkdownOptions } from './markdown';
5
5
  export { buildCSVContent, downloadCSV, escapeCSVCell, sanitizeFilename } from './csv-utils';
6
+ export { readStoredNumber, writeStoredNumber, removeStoredValue, type StorageKind, type StorageOptions, type WriteStorageOptions } from './storage-utils';
@@ -3,6 +3,7 @@ import { InfiniteScrollController } from "./infinite-scroll-controller.mjs";
3
3
  import { KeyboardNavigationController } from "./keyboard-navigation-controller.mjs";
4
4
  import { renderInlineMarkdown, renderMarkdown } from "./markdown.mjs";
5
5
  import { buildCSVContent, downloadCSV, escapeCSVCell, sanitizeFilename } from "./csv-utils.mjs";
6
+ import { readStoredNumber, removeStoredValue, writeStoredNumber } from "./storage-utils.mjs";
6
7
  export {
7
8
  DeleteThreadController,
8
9
  InfiniteScrollController,
@@ -10,7 +11,10 @@ export {
10
11
  buildCSVContent,
11
12
  downloadCSV,
12
13
  escapeCSVCell,
14
+ readStoredNumber,
15
+ removeStoredValue,
13
16
  renderInlineMarkdown,
14
17
  renderMarkdown,
15
- sanitizeFilename
18
+ sanitizeFilename,
19
+ writeStoredNumber
16
20
  };
@@ -0,0 +1,26 @@
1
+ export type StorageKind = 'local' | 'session';
2
+ export interface StorageOptions {
3
+ /** Which Web Storage area to use. `session` is per-tab, `local` is shared across tabs on the origin. */
4
+ kind: StorageKind;
5
+ /** The storage key. */
6
+ key: string;
7
+ }
8
+ export interface WriteStorageOptions extends StorageOptions {
9
+ /** The number to store. */
10
+ value: number;
11
+ }
12
+ /**
13
+ * Reads a numeric value from Web Storage.
14
+ *
15
+ * @returns The parsed number, or `undefined` if the key is missing, not a finite number, or storage is unavailable.
16
+ */
17
+ export declare function readStoredNumber({ kind, key }: StorageOptions): number | undefined;
18
+ /**
19
+ * Writes a numeric value to Web Storage. Fails silently if storage is unavailable
20
+ * (private mode, sandboxed iframe, quota exceeded).
21
+ */
22
+ export declare function writeStoredNumber({ kind, key, value }: WriteStorageOptions): void;
23
+ /**
24
+ * Removes a value from Web Storage. Fails silently if storage is unavailable.
25
+ */
26
+ export declare function removeStoredValue({ kind, key }: StorageOptions): void;
@@ -0,0 +1,32 @@
1
+ function resolveStorage(kind) {
2
+ return kind === "session" ? window.sessionStorage : window.localStorage;
3
+ }
4
+ function readStoredNumber({ kind, key }) {
5
+ try {
6
+ const raw = resolveStorage(kind).getItem(key);
7
+ if (raw === null) {
8
+ return void 0;
9
+ }
10
+ const value = Number(raw);
11
+ return Number.isFinite(value) ? value : void 0;
12
+ } catch {
13
+ return void 0;
14
+ }
15
+ }
16
+ function writeStoredNumber({ kind, key, value }) {
17
+ try {
18
+ resolveStorage(kind).setItem(key, String(value));
19
+ } catch {
20
+ }
21
+ }
22
+ function removeStoredValue({ kind, key }) {
23
+ try {
24
+ resolveStorage(kind).removeItem(key);
25
+ } catch {
26
+ }
27
+ }
28
+ export {
29
+ readStoredNumber,
30
+ removeStoredValue,
31
+ writeStoredNumber
32
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tylertech/forge-ai",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "A library of Tyler Forge™ AI chat interface web components.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Tyler Technologies, Inc.",