@vortexm/vjt 0.1.15 → 0.1.17

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.
package/dist/index.js CHANGED
@@ -3865,7 +3865,7 @@ var NetworkRuntime = class {
3865
3865
  this.rerenderRoot = options.rerenderRoot;
3866
3866
  }
3867
3867
  connectSseStreams() {
3868
- if (typeof window === "undefined" || typeof EventSource === "undefined" || this.eventSources.length > 0) {
3868
+ if (typeof window === "undefined" || this.eventSources.length > 0) {
3869
3869
  return;
3870
3870
  }
3871
3871
  for (const config of this.sseConfigs) {
@@ -3877,26 +3877,10 @@ var NetworkRuntime = class {
3877
3877
  logRuntimeError("sseConfig", new Error("Blocked unsafe SSE url"), { url: config.url });
3878
3878
  continue;
3879
3879
  }
3880
- const source = new EventSource(safeUrl);
3881
- this.eventSources.push(source);
3882
- for (const eventDefinition of config.events ?? []) {
3883
- if (!eventDefinition?.name) {
3884
- continue;
3885
- }
3886
- source.addEventListener(eventDefinition.name, (event) => {
3887
- void this.handleSseEvent(eventDefinition, event).catch((error) => {
3888
- logRuntimeError("handleSseEvent", error, {
3889
- eventName: eventDefinition.name,
3890
- url: safeUrl
3891
- });
3892
- });
3893
- });
3880
+ const connection = this.createSseConnection(config, safeUrl);
3881
+ if (connection) {
3882
+ this.eventSources.push(connection);
3894
3883
  }
3895
- source.onerror = (event) => {
3896
- logRuntimeError("sseConnection", event, {
3897
- url: config.url
3898
- });
3899
- };
3900
3884
  }
3901
3885
  }
3902
3886
  async executeRequest(requestName, currentValue) {
@@ -4099,21 +4083,309 @@ var NetworkRuntime = class {
4099
4083
  return this.stringifyPrimitive(value);
4100
4084
  }
4101
4085
  }
4102
- async handleSseEvent(eventDefinition, event) {
4086
+ createSseConnection(config, safeUrl) {
4087
+ if (typeof fetch === "function" && typeof AbortController !== "undefined" && typeof TextDecoder !== "undefined" && typeof ReadableStream !== "undefined") {
4088
+ return this.createFetchSseConnection(config, safeUrl);
4089
+ }
4090
+ if (typeof EventSource !== "undefined") {
4091
+ return this.createNativeSseConnection(config, safeUrl);
4092
+ }
4093
+ logRuntimeError("sseConnection", new Error("SSE is not supported in this browser"), {
4094
+ url: safeUrl
4095
+ });
4096
+ return null;
4097
+ }
4098
+ createNativeSseConnection(config, safeUrl) {
4099
+ const source = new EventSource(safeUrl);
4100
+ source.onopen = () => {
4101
+ logRuntimeDebug(this.debugLogging, "sse-open", {
4102
+ url: safeUrl,
4103
+ transport: "event-source"
4104
+ });
4105
+ };
4106
+ source.onmessage = (event) => {
4107
+ const payload = typeof event.data === "string" ? event.data : "";
4108
+ logRuntimeDebug(this.debugLogging, "sse-message", {
4109
+ url: safeUrl,
4110
+ transport: "event-source",
4111
+ eventName: "message",
4112
+ payloadLength: payload.length
4113
+ });
4114
+ };
4115
+ for (const eventDefinition of config.events ?? []) {
4116
+ if (!eventDefinition?.name) {
4117
+ continue;
4118
+ }
4119
+ source.addEventListener(eventDefinition.name, (event) => {
4120
+ void this.handleSseEvent(eventDefinition, event.data ?? "", safeUrl).catch((error) => {
4121
+ logRuntimeError("handleSseEvent", error, {
4122
+ eventName: eventDefinition.name,
4123
+ url: safeUrl
4124
+ });
4125
+ });
4126
+ });
4127
+ }
4128
+ source.onerror = (event) => {
4129
+ logRuntimeError("sseConnection", event, {
4130
+ url: config.url,
4131
+ transport: "event-source"
4132
+ });
4133
+ };
4134
+ logRuntimeDebug(this.debugLogging, "sse-connect", {
4135
+ url: safeUrl,
4136
+ transport: "event-source"
4137
+ });
4138
+ return {
4139
+ close: () => {
4140
+ source.close();
4141
+ }
4142
+ };
4143
+ }
4144
+ createFetchSseConnection(config, safeUrl) {
4145
+ let closed = false;
4146
+ let reconnectDelayMs = 2e3;
4147
+ let reconnectTimeoutId = null;
4148
+ let activeAbortController = null;
4149
+ let lastEventId = "";
4150
+ let connectionAttempt = 0;
4151
+ const clearReconnectTimeout = () => {
4152
+ if (reconnectTimeoutId !== null && typeof window !== "undefined") {
4153
+ window.clearTimeout(reconnectTimeoutId);
4154
+ }
4155
+ reconnectTimeoutId = null;
4156
+ };
4157
+ const scheduleReconnect = (reason) => {
4158
+ if (closed || typeof window === "undefined") {
4159
+ return;
4160
+ }
4161
+ logRuntimeDebug(this.debugLogging, "sse-reconnect-scheduled", {
4162
+ url: safeUrl,
4163
+ transport: "fetch",
4164
+ delayMs: reconnectDelayMs,
4165
+ reason,
4166
+ lastEventId
4167
+ });
4168
+ clearReconnectTimeout();
4169
+ reconnectTimeoutId = window.setTimeout(() => {
4170
+ reconnectTimeoutId = null;
4171
+ void connectLoop().catch((error) => {
4172
+ logRuntimeError("sseConnection", error, {
4173
+ url: safeUrl,
4174
+ transport: "fetch"
4175
+ });
4176
+ });
4177
+ }, reconnectDelayMs);
4178
+ };
4179
+ const flushSseChunk = (eventName, dataLines) => {
4180
+ if (!dataLines.length) {
4181
+ return;
4182
+ }
4183
+ const payload = dataLines.join("\n");
4184
+ const matchingDefinitions = (config.events ?? []).filter((definition) => definition?.name === eventName);
4185
+ logRuntimeDebug(this.debugLogging, "sse-dispatch", {
4186
+ url: safeUrl,
4187
+ transport: "fetch",
4188
+ eventName,
4189
+ payloadLength: payload.length,
4190
+ matchedHandlers: matchingDefinitions.length,
4191
+ lastEventId
4192
+ });
4193
+ if (!matchingDefinitions.length) {
4194
+ return;
4195
+ }
4196
+ for (const eventDefinition of matchingDefinitions) {
4197
+ void this.handleSseEvent(eventDefinition, payload, safeUrl).catch((error) => {
4198
+ logRuntimeError("handleSseEvent", error, {
4199
+ eventName,
4200
+ url: safeUrl
4201
+ });
4202
+ });
4203
+ }
4204
+ };
4205
+ const processSseText = (chunk) => {
4206
+ let eventName = "message";
4207
+ let dataLines = [];
4208
+ const normalized = chunk.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
4209
+ const lines = normalized.split("\n");
4210
+ for (const line of lines) {
4211
+ if (!line) {
4212
+ flushSseChunk(eventName, dataLines);
4213
+ eventName = "message";
4214
+ dataLines = [];
4215
+ continue;
4216
+ }
4217
+ if (line.startsWith(":")) {
4218
+ continue;
4219
+ }
4220
+ const separatorIndex = line.indexOf(":");
4221
+ const field = separatorIndex >= 0 ? line.slice(0, separatorIndex) : line;
4222
+ let value = separatorIndex >= 0 ? line.slice(separatorIndex + 1) : "";
4223
+ if (value.startsWith(" ")) {
4224
+ value = value.slice(1);
4225
+ }
4226
+ switch (field) {
4227
+ case "event":
4228
+ eventName = value || "message";
4229
+ break;
4230
+ case "data":
4231
+ dataLines.push(value);
4232
+ break;
4233
+ case "id":
4234
+ if (!value.includes("\0")) {
4235
+ lastEventId = value;
4236
+ }
4237
+ break;
4238
+ case "retry": {
4239
+ const parsedDelay = Number.parseInt(value, 10);
4240
+ if (Number.isFinite(parsedDelay) && parsedDelay >= 0) {
4241
+ reconnectDelayMs = parsedDelay;
4242
+ }
4243
+ break;
4244
+ }
4245
+ default:
4246
+ break;
4247
+ }
4248
+ }
4249
+ flushSseChunk(eventName, dataLines);
4250
+ };
4251
+ const findEventBoundary = (value) => value.search(/\r?\n\r?\n/);
4252
+ const connectLoop = async () => {
4253
+ if (closed) {
4254
+ return;
4255
+ }
4256
+ connectionAttempt += 1;
4257
+ activeAbortController = new AbortController();
4258
+ let reconnectReason = "stream-ended";
4259
+ try {
4260
+ logRuntimeDebug(this.debugLogging, "sse-connect-attempt", {
4261
+ url: safeUrl,
4262
+ transport: "fetch",
4263
+ attempt: connectionAttempt,
4264
+ lastEventId
4265
+ });
4266
+ const headers = {
4267
+ Accept: "text/event-stream"
4268
+ };
4269
+ if (lastEventId) {
4270
+ headers["Last-Event-ID"] = lastEventId;
4271
+ }
4272
+ const response = await fetch(safeUrl, {
4273
+ method: "GET",
4274
+ headers,
4275
+ cache: "no-store",
4276
+ credentials: "same-origin",
4277
+ signal: activeAbortController.signal
4278
+ });
4279
+ if (!response.ok) {
4280
+ throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);
4281
+ }
4282
+ const contentType = response.headers.get("content-type") ?? "";
4283
+ if (!contentType.toLowerCase().includes("text/event-stream")) {
4284
+ const previewText = await response.text().catch(() => "");
4285
+ const normalizedPreview = previewText.replaceAll(/\s+/g, " ").trim().slice(0, 240);
4286
+ const error = new Error(`SSE response has unexpected content-type: ${contentType || "<empty>"}`);
4287
+ error.sseDetails = {
4288
+ status: response.status,
4289
+ statusText: response.statusText,
4290
+ responseUrl: response.url,
4291
+ redirected: response.redirected,
4292
+ contentType,
4293
+ bodyPreview: normalizedPreview
4294
+ };
4295
+ throw error;
4296
+ }
4297
+ logRuntimeDebug(this.debugLogging, "sse-open", {
4298
+ url: safeUrl,
4299
+ transport: "fetch",
4300
+ status: response.status,
4301
+ contentType,
4302
+ attempt: connectionAttempt,
4303
+ lastEventId,
4304
+ requestUsesLastEventIdHeader: Boolean(lastEventId)
4305
+ });
4306
+ if (!response.body || typeof response.body.getReader !== "function") {
4307
+ throw new Error("SSE streaming is not available in fetch response body");
4308
+ }
4309
+ const reader = response.body.getReader();
4310
+ const decoder = new TextDecoder();
4311
+ let buffer = "";
4312
+ let receivedChunkCount = 0;
4313
+ let receivedByteCount = 0;
4314
+ while (!closed) {
4315
+ const { done, value } = await reader.read();
4316
+ if (done) {
4317
+ break;
4318
+ }
4319
+ receivedChunkCount += 1;
4320
+ receivedByteCount += value?.byteLength ?? 0;
4321
+ buffer += decoder.decode(value, { stream: true });
4322
+ logRuntimeDebug(this.debugLogging, "sse-chunk", {
4323
+ url: safeUrl,
4324
+ transport: "fetch",
4325
+ chunkIndex: receivedChunkCount,
4326
+ chunkBytes: value?.byteLength ?? 0,
4327
+ totalBytes: receivedByteCount,
4328
+ bufferedChars: buffer.length
4329
+ });
4330
+ let boundaryIndex = findEventBoundary(buffer);
4331
+ while (boundaryIndex >= 0) {
4332
+ const rawChunk = buffer.slice(0, boundaryIndex);
4333
+ processSseText(rawChunk);
4334
+ buffer = buffer.slice(boundaryIndex).replace(/^\r?\n\r?\n/, "");
4335
+ boundaryIndex = findEventBoundary(buffer);
4336
+ }
4337
+ }
4338
+ buffer += decoder.decode();
4339
+ if (buffer.trim()) {
4340
+ processSseText(buffer);
4341
+ }
4342
+ } catch (error) {
4343
+ if (closed || error instanceof DOMException && error.name === "AbortError") {
4344
+ return;
4345
+ }
4346
+ reconnectReason = error instanceof Error ? error.message : "stream-error";
4347
+ logRuntimeError("sseConnection", error, {
4348
+ url: safeUrl,
4349
+ transport: "fetch",
4350
+ ...typeof error === "object" && error !== null && "sseDetails" in error ? { response: error.sseDetails } : {}
4351
+ });
4352
+ } finally {
4353
+ activeAbortController = null;
4354
+ }
4355
+ scheduleReconnect(reconnectReason);
4356
+ };
4357
+ const connection = {
4358
+ close: () => {
4359
+ closed = true;
4360
+ clearReconnectTimeout();
4361
+ activeAbortController?.abort();
4362
+ activeAbortController = null;
4363
+ }
4364
+ };
4365
+ void connectLoop().catch((error) => {
4366
+ logRuntimeError("sseConnection", error, {
4367
+ url: safeUrl,
4368
+ transport: "fetch"
4369
+ });
4370
+ });
4371
+ return connection;
4372
+ }
4373
+ async handleSseEvent(eventDefinition, rawData, url) {
4103
4374
  let parsedMessage = {};
4104
4375
  try {
4105
- parsedMessage = event.data ? JSON.parse(event.data) : {};
4376
+ parsedMessage = rawData ? JSON.parse(rawData) : {};
4106
4377
  } catch {
4107
- parsedMessage = { value: event.data ?? "" };
4378
+ parsedMessage = { value: rawData ?? "" };
4108
4379
  }
4109
4380
  const message = eventDefinition.message ? sanitizeSchemaValue(eventDefinition.message, parsedMessage, `sse.${eventDefinition.name}`) : (() => {
4110
4381
  throw new Error(`SSE event ${eventDefinition.name} must define message schema`);
4111
4382
  })();
4112
4383
  logRuntimeDebug(this.debugLogging, "sse", {
4113
4384
  eventName: eventDefinition.name,
4114
- raw: event.data,
4385
+ raw: rawData,
4115
4386
  parsedMessage,
4116
- message
4387
+ message,
4388
+ url
4117
4389
  });
4118
4390
  if (!eventDefinition.onEvent?.length) {
4119
4391
  return;
@@ -4479,6 +4751,10 @@ var ReferenceRuntime = class {
4479
4751
  return readPath(state, ["value"]);
4480
4752
  case "isHidden":
4481
4753
  return state.isHidden ?? true;
4754
+ case "isScrolledToTop":
4755
+ return state.isScrolledToTop ?? true;
4756
+ case "isScrolledToBottom":
4757
+ return state.isScrolledToBottom ?? true;
4482
4758
  case "url":
4483
4759
  return resolveInlineI18nValue(this.host, state.url ?? "");
4484
4760
  case "base64":
@@ -7720,6 +7996,19 @@ function isGeneratedElementId(value) {
7720
7996
  function isStepperEdit(node) {
7721
7997
  return node.type === "number" || node.type === "float";
7722
7998
  }
7999
+ function isScrollableWidgetNode(node) {
8000
+ switch (node.widget) {
8001
+ case "list":
8002
+ case "grid-view":
8003
+ case "listbox":
8004
+ case "combobox":
8005
+ return true;
8006
+ case "container-layout":
8007
+ return normalizeBoolean(node.verticallyScrollable, false);
8008
+ default:
8009
+ return false;
8010
+ }
8011
+ }
7723
8012
  function getDateFormat(node) {
7724
8013
  return node.format === "yyyy-MM-dd" ? "yyyy-MM-dd" : DEFAULT_DATE_FORMAT;
7725
8014
  }
@@ -8056,6 +8345,7 @@ var RuntimeRenderer = class {
8056
8345
  this.attachInputBehavior(this.root);
8057
8346
  this.attachWidgetEvents(this.root);
8058
8347
  restoreElementState(this.root, preservedState, this.stateByKey);
8348
+ this.syncAllScrollStateFlags();
8059
8349
  this.applyPendingScrollAction();
8060
8350
  this.applyPendingCursorAction();
8061
8351
  this.activeContextMenu = adjustContextMenuPosition(this.root, this.activeContextMenu);
@@ -8674,7 +8964,9 @@ var RuntimeRenderer = class {
8674
8964
  enabled: normalizeBoolean(node.enabled, true),
8675
8965
  definitionSignature: buildDefinitionSignature(node.elements ?? []),
8676
8966
  elements: deepClone(node.elements ?? []),
8677
- selected: toFiniteNumber3(node.item) ?? toFiniteNumber3(node.selected) ?? -1
8967
+ selected: toFiniteNumber3(node.item) ?? toFiniteNumber3(node.selected) ?? -1,
8968
+ isScrolledToTop: true,
8969
+ isScrolledToBottom: true
8678
8970
  };
8679
8971
  break;
8680
8972
  case "listbox":
@@ -8686,7 +8978,9 @@ var RuntimeRenderer = class {
8686
8978
  enabled: normalizeBoolean(node.enabled, true),
8687
8979
  definitionSignature: buildDefinitionSignature(node.elements ?? []),
8688
8980
  listboxElements: deepClone(node.elements ?? []),
8689
- selected: toFiniteNumber3(node.selected) ?? 0
8981
+ selected: toFiniteNumber3(node.selected) ?? 0,
8982
+ isScrolledToTop: true,
8983
+ isScrolledToBottom: true
8690
8984
  };
8691
8985
  break;
8692
8986
  case "combobox":
@@ -8698,7 +8992,9 @@ var RuntimeRenderer = class {
8698
8992
  enabled: normalizeBoolean(node.enabled, true),
8699
8993
  definitionSignature: buildDefinitionSignature(node.elements ?? []),
8700
8994
  comboboxElements: deepClone(node.elements ?? []),
8701
- selected: toFiniteNumber3(node.selected) ?? 0
8995
+ selected: toFiniteNumber3(node.selected) ?? 0,
8996
+ isScrolledToTop: true,
8997
+ isScrolledToBottom: true
8702
8998
  };
8703
8999
  break;
8704
9000
  case "radio-group":
@@ -8750,7 +9046,9 @@ var RuntimeRenderer = class {
8750
9046
  widget: node.widget,
8751
9047
  id: stateId,
8752
9048
  name: node.name,
8753
- enabled: normalizeBoolean(node.enabled, true)
9049
+ enabled: normalizeBoolean(node.enabled, true),
9050
+ isScrolledToTop: isScrollableWidgetNode(node) ? true : void 0,
9051
+ isScrolledToBottom: isScrollableWidgetNode(node) ? true : void 0
8754
9052
  };
8755
9053
  break;
8756
9054
  }
@@ -9207,6 +9505,19 @@ var RuntimeRenderer = class {
9207
9505
  redispatchUnderlyingClick: (x, y) => this.redispatchUnderlyingClick(x, y),
9208
9506
  runActions: (actions, inputValue, context) => this.actionRuntime.runActions(actions, inputValue, context)
9209
9507
  });
9508
+ for (const element of Array.from(root.querySelectorAll("[data-widget-key]"))) {
9509
+ const key = element.dataset.widgetKey;
9510
+ if (!key) {
9511
+ continue;
9512
+ }
9513
+ const node = this.nodeByKey.get(key);
9514
+ if (!node || !isScrollableWidgetNode(node)) {
9515
+ continue;
9516
+ }
9517
+ element.addEventListener("scroll", () => {
9518
+ this.syncScrollStateForElement(element, key);
9519
+ }, { passive: true });
9520
+ }
9210
9521
  }
9211
9522
  updateOwningListElementDescriptor(element, mutate) {
9212
9523
  const listElement = element.closest(".vjt-list-element");
@@ -9525,6 +9836,7 @@ var RuntimeRenderer = class {
9525
9836
  }
9526
9837
  if (typeof window === "undefined") {
9527
9838
  initialElement.scrollTop = Math.max(0, Math.min(targetTop, getMaxScrollTop(initialElement)));
9839
+ this.syncScrollStateForReference(reference, initialElement);
9528
9840
  return;
9529
9841
  }
9530
9842
  if (this.activeScrollAnimationFrameId !== null) {
@@ -9536,6 +9848,7 @@ var RuntimeRenderer = class {
9536
9848
  const delta = clampedTargetTop - startTop;
9537
9849
  if (Math.abs(delta) < 1) {
9538
9850
  initialElement.scrollTop = clampedTargetTop;
9851
+ this.syncScrollStateForReference(reference, initialElement);
9539
9852
  return;
9540
9853
  }
9541
9854
  const durationMs = 300;
@@ -9551,10 +9864,12 @@ var RuntimeRenderer = class {
9551
9864
  const progress = Math.min(1, elapsed / durationMs);
9552
9865
  const liveTargetTop = Math.max(0, Math.min(clampedTargetTop, getMaxScrollTop(liveElement)));
9553
9866
  liveElement.scrollTop = startTop + (liveTargetTop - startTop) * easeInOutCubic(progress);
9867
+ this.syncScrollStateForReference(reference, liveElement);
9554
9868
  if (progress < 1) {
9555
9869
  this.activeScrollAnimationFrameId = window.requestAnimationFrame(tick);
9556
9870
  } else {
9557
9871
  liveElement.scrollTop = liveTargetTop;
9872
+ this.syncScrollStateForReference(reference, liveElement);
9558
9873
  this.activeScrollAnimationFrameId = null;
9559
9874
  }
9560
9875
  };
@@ -9608,6 +9923,48 @@ var RuntimeRenderer = class {
9608
9923
  }
9609
9924
  return null;
9610
9925
  }
9926
+ syncAllScrollStateFlags() {
9927
+ if (!(this.root instanceof HTMLElement)) {
9928
+ return;
9929
+ }
9930
+ for (const element of Array.from(this.root.querySelectorAll("[data-widget-key]"))) {
9931
+ const key = element.dataset.widgetKey;
9932
+ if (!key) {
9933
+ continue;
9934
+ }
9935
+ const node = this.nodeByKey.get(key);
9936
+ if (!node || !isScrollableWidgetNode(node)) {
9937
+ continue;
9938
+ }
9939
+ this.syncScrollStateForElement(element, key);
9940
+ }
9941
+ }
9942
+ syncScrollStateForReference(reference, element) {
9943
+ const widgetId = reference.split(".")[0]?.trim();
9944
+ if (!widgetId) {
9945
+ return;
9946
+ }
9947
+ const targetElement = element ?? this.findScrollableWidgetElement(reference);
9948
+ if (!(targetElement instanceof HTMLElement)) {
9949
+ return;
9950
+ }
9951
+ const key = targetElement.dataset.widgetKey ?? widgetId;
9952
+ this.syncScrollStateForElement(targetElement, key);
9953
+ }
9954
+ syncScrollStateForElement(element, key) {
9955
+ const state = this.stateByKey.get(key) ?? this.stateById.get(key);
9956
+ if (!state) {
9957
+ return;
9958
+ }
9959
+ const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
9960
+ if (maxScrollTop <= 1) {
9961
+ state.isScrolledToTop = true;
9962
+ state.isScrolledToBottom = true;
9963
+ return;
9964
+ }
9965
+ state.isScrolledToTop = element.scrollTop <= 1;
9966
+ state.isScrolledToBottom = element.scrollTop >= maxScrollTop - 1;
9967
+ }
9611
9968
  toScrollableIndex(value) {
9612
9969
  if (typeof value === "number" && Number.isFinite(value)) {
9613
9970
  return Math.max(0, Math.trunc(value));
@@ -1,4 +1,4 @@
1
- import type { ActionDefinition, PrimitiveRequestType, RequestMapInput, RequestSchema, SseConfig } from './types.js';
1
+ import type { ActionDefinition, ClosableRuntimeResource, PrimitiveRequestType, RequestMapInput, RequestSchema, SseConfig } from './types.js';
2
2
  type ActionRunner = (actions: ActionDefinition[], inputValue: unknown, context: {
3
3
  currentValue: unknown;
4
4
  responseValue?: unknown;
@@ -8,7 +8,7 @@ type NetworkRuntimeOptions = {
8
8
  requestsMap: RequestMapInput;
9
9
  sseConfigs: SseConfig[];
10
10
  backendUrl?: string;
11
- eventSources: EventSource[];
11
+ eventSources: ClosableRuntimeResource[];
12
12
  runActions: ActionRunner;
13
13
  rerenderRoot: () => Promise<void>;
14
14
  };
@@ -28,6 +28,9 @@ export declare class NetworkRuntime {
28
28
  private createRequestError;
29
29
  private toRequestErrorPayload;
30
30
  coercePrimitiveSchemaValue(type: PrimitiveRequestType, value: unknown): unknown;
31
+ private createSseConnection;
32
+ private createNativeSseConnection;
33
+ private createFetchSseConnection;
31
34
  private handleSseEvent;
32
35
  }
33
36
  export {};
@@ -6,6 +6,9 @@ export type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
6
6
  export type WidgetEventName = 'onClick' | 'onUserValueChange' | 'onRefresh' | 'onEnter' | 'onShiftEnter' | 'onControlEnter' | 'onPaste';
7
7
  export type SystemEventName = 'onBeforeRender' | 'onAfterRender' | 'onBeforeNavigate' | 'onAfterNavigate' | 'onLayoutSwitchToMobile' | 'onLayoutSwitchToDesktop' | 'onSpeechDetected' | 'onRecordingStarted' | 'onRecordingStopped' | 'onRecordingError' | 'onListeningError' | 'onListeringError' | 'onPlayFinished' | 'onPlayingStopped';
8
8
  export type PrimitiveRequestType = 'int' | 'float' | 'boolean' | 'string';
9
+ export type ClosableRuntimeResource = {
10
+ close: () => void;
11
+ };
9
12
  export type RouteDefinition = {
10
13
  path: string;
11
14
  ui: string;
@@ -128,6 +131,8 @@ export type WidgetState = {
128
131
  modalHeight?: number;
129
132
  activeTab?: number;
130
133
  isHidden?: boolean;
134
+ isScrolledToTop?: boolean;
135
+ isScrolledToBottom?: boolean;
131
136
  };
132
137
  export type RuntimeSnapshot = {
133
138
  stateByKey: Array<[string, WidgetState]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vortexm/vjt",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/vjt-styles.css CHANGED
@@ -557,6 +557,12 @@ body {
557
557
  grid-area: 1 / 1;
558
558
  min-width: 0;
559
559
  min-height: 0;
560
+ position: relative;
561
+ pointer-events: none;
562
+ }
563
+
564
+ .vjt-overlay-layer > * {
565
+ pointer-events: auto;
560
566
  }
561
567
 
562
568
  .vjt-overlay-layer > .vjt-static-text,