@ai-gui/plugin-form 0.4.1 → 0.4.4
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.cjs +24 -10
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +24 -10
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -94,7 +94,7 @@ function form(options) {
|
|
|
94
94
|
}
|
|
95
95
|
const output = {
|
|
96
96
|
kind: "mount",
|
|
97
|
-
mount: (host) => mountForm(host, parsed.data, options
|
|
97
|
+
mount: (host) => mountForm(host, parsed.data, options)
|
|
98
98
|
};
|
|
99
99
|
outputs.set(node, output);
|
|
100
100
|
return output;
|
|
@@ -303,13 +303,14 @@ function parseOptions(value, path, issues) {
|
|
|
303
303
|
}] : [];
|
|
304
304
|
});
|
|
305
305
|
}
|
|
306
|
-
function mountForm(host, definition,
|
|
306
|
+
function mountForm(host, definition, options) {
|
|
307
307
|
const instanceId = String(++nextFormInstanceId);
|
|
308
308
|
const instancePrefix = `aigui-form-${instanceId}-${definition.id}`;
|
|
309
309
|
const cardType = `form:${definition.id}:${instanceId}`;
|
|
310
310
|
const owner = {};
|
|
311
311
|
const controller = new AbortController();
|
|
312
312
|
let pending = false;
|
|
313
|
+
let submitted = options.submitted === true;
|
|
313
314
|
let disposed = false;
|
|
314
315
|
const formElement = document.createElement("form");
|
|
315
316
|
formElement.noValidate = true;
|
|
@@ -329,18 +330,28 @@ function mountForm(host, definition, runtime) {
|
|
|
329
330
|
actionError.hidden = true;
|
|
330
331
|
formElement.appendChild(actionError);
|
|
331
332
|
const submit = document.createElement("button");
|
|
332
|
-
submit.type = "
|
|
333
|
+
submit.type = "button";
|
|
334
|
+
submit.setAttribute("data-aigui-form-submit", "");
|
|
333
335
|
submit.textContent = definition.submitLabel ?? "Submit";
|
|
334
336
|
formElement.appendChild(submit);
|
|
337
|
+
const markSubmitted = () => {
|
|
338
|
+
if (disposed) return;
|
|
339
|
+
pending = false;
|
|
340
|
+
submitted = true;
|
|
341
|
+
formElement.removeAttribute("aria-busy");
|
|
342
|
+
formElement.setAttribute("data-aigui-form-submitted", "");
|
|
343
|
+
for (const element of Array.from(formElement.elements)) if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element instanceof HTMLButtonElement || element instanceof HTMLFieldSetElement) element.disabled = true;
|
|
344
|
+
submit.textContent = options.submittedLabel ?? "Submitted";
|
|
345
|
+
};
|
|
346
|
+
if (submitted) markSubmitted();
|
|
335
347
|
const onInput = (event) => {
|
|
336
348
|
const control = event.target;
|
|
337
349
|
if (!(control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement || control instanceof HTMLSelectElement)) return;
|
|
338
350
|
clearFieldError(control.name, controls, errorElements);
|
|
339
351
|
actionError.hidden = true;
|
|
340
352
|
};
|
|
341
|
-
const onSubmit = (
|
|
342
|
-
|
|
343
|
-
if (pending || disposed) return;
|
|
353
|
+
const onSubmit = () => {
|
|
354
|
+
if (pending || submitted || disposed) return;
|
|
344
355
|
const validation = validateFormValues(definition, readControls(definition, controls));
|
|
345
356
|
renderErrors(validation.errors, controls, errorElements);
|
|
346
357
|
actionError.hidden = true;
|
|
@@ -357,14 +368,14 @@ function mountForm(host, definition, runtime) {
|
|
|
357
368
|
submit.disabled = false;
|
|
358
369
|
formElement.removeAttribute("aria-busy");
|
|
359
370
|
};
|
|
360
|
-
|
|
371
|
+
options.actionRuntime.dispatch({
|
|
361
372
|
type: definition.submitAction,
|
|
362
373
|
params: validation.values,
|
|
363
374
|
cardType
|
|
364
375
|
}, {
|
|
365
376
|
owner,
|
|
366
377
|
signal: controller.signal
|
|
367
|
-
}).then(
|
|
378
|
+
}).then(markSubmitted, (error) => {
|
|
368
379
|
if (!disposed && !controller.signal.aborted) {
|
|
369
380
|
actionError.textContent = actionErrorMessage(error);
|
|
370
381
|
actionError.hidden = false;
|
|
@@ -372,16 +383,19 @@ function mountForm(host, definition, runtime) {
|
|
|
372
383
|
settle();
|
|
373
384
|
});
|
|
374
385
|
};
|
|
386
|
+
const preventImplicitSubmit = (event) => event.preventDefault();
|
|
375
387
|
formElement.addEventListener("input", onInput);
|
|
376
388
|
formElement.addEventListener("change", onInput);
|
|
377
|
-
formElement.addEventListener("submit",
|
|
389
|
+
formElement.addEventListener("submit", preventImplicitSubmit);
|
|
390
|
+
submit.addEventListener("click", onSubmit);
|
|
378
391
|
host.replaceChildren(formElement);
|
|
379
392
|
return () => {
|
|
380
393
|
disposed = true;
|
|
381
394
|
controller.abort();
|
|
382
395
|
formElement.removeEventListener("input", onInput);
|
|
383
396
|
formElement.removeEventListener("change", onInput);
|
|
384
|
-
formElement.removeEventListener("submit",
|
|
397
|
+
formElement.removeEventListener("submit", preventImplicitSubmit);
|
|
398
|
+
submit.removeEventListener("click", onSubmit);
|
|
385
399
|
};
|
|
386
400
|
}
|
|
387
401
|
function createField(formId, field) {
|
package/dist/index.d.cts
CHANGED
|
@@ -56,6 +56,10 @@ interface FormValidationResult {
|
|
|
56
56
|
interface FormPluginOptions {
|
|
57
57
|
/** Shared core runtime whose registry is the only allowlist for submitAction. */
|
|
58
58
|
actionRuntime: ActionRuntime;
|
|
59
|
+
/** Mount forms as already submitted, useful when restoring persisted conversations. */
|
|
60
|
+
submitted?: boolean;
|
|
61
|
+
/** Label shown after a successful or restored submission. */
|
|
62
|
+
submittedLabel?: string;
|
|
59
63
|
}
|
|
60
64
|
declare function formPromptSpec(): string;
|
|
61
65
|
declare function form(options: FormPluginOptions): AIGuiPlugin;
|
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,10 @@ interface FormValidationResult {
|
|
|
56
56
|
interface FormPluginOptions {
|
|
57
57
|
/** Shared core runtime whose registry is the only allowlist for submitAction. */
|
|
58
58
|
actionRuntime: ActionRuntime;
|
|
59
|
+
/** Mount forms as already submitted, useful when restoring persisted conversations. */
|
|
60
|
+
submitted?: boolean;
|
|
61
|
+
/** Label shown after a successful or restored submission. */
|
|
62
|
+
submittedLabel?: string;
|
|
59
63
|
}
|
|
60
64
|
declare function formPromptSpec(): string;
|
|
61
65
|
declare function form(options: FormPluginOptions): AIGuiPlugin;
|
package/dist/index.js
CHANGED
|
@@ -70,7 +70,7 @@ function form(options) {
|
|
|
70
70
|
}
|
|
71
71
|
const output = {
|
|
72
72
|
kind: "mount",
|
|
73
|
-
mount: (host) => mountForm(host, parsed.data, options
|
|
73
|
+
mount: (host) => mountForm(host, parsed.data, options)
|
|
74
74
|
};
|
|
75
75
|
outputs.set(node, output);
|
|
76
76
|
return output;
|
|
@@ -279,13 +279,14 @@ function parseOptions(value, path, issues) {
|
|
|
279
279
|
}] : [];
|
|
280
280
|
});
|
|
281
281
|
}
|
|
282
|
-
function mountForm(host, definition,
|
|
282
|
+
function mountForm(host, definition, options) {
|
|
283
283
|
const instanceId = String(++nextFormInstanceId);
|
|
284
284
|
const instancePrefix = `aigui-form-${instanceId}-${definition.id}`;
|
|
285
285
|
const cardType = `form:${definition.id}:${instanceId}`;
|
|
286
286
|
const owner = {};
|
|
287
287
|
const controller = new AbortController();
|
|
288
288
|
let pending = false;
|
|
289
|
+
let submitted = options.submitted === true;
|
|
289
290
|
let disposed = false;
|
|
290
291
|
const formElement = document.createElement("form");
|
|
291
292
|
formElement.noValidate = true;
|
|
@@ -305,18 +306,28 @@ function mountForm(host, definition, runtime) {
|
|
|
305
306
|
actionError.hidden = true;
|
|
306
307
|
formElement.appendChild(actionError);
|
|
307
308
|
const submit = document.createElement("button");
|
|
308
|
-
submit.type = "
|
|
309
|
+
submit.type = "button";
|
|
310
|
+
submit.setAttribute("data-aigui-form-submit", "");
|
|
309
311
|
submit.textContent = definition.submitLabel ?? "Submit";
|
|
310
312
|
formElement.appendChild(submit);
|
|
313
|
+
const markSubmitted = () => {
|
|
314
|
+
if (disposed) return;
|
|
315
|
+
pending = false;
|
|
316
|
+
submitted = true;
|
|
317
|
+
formElement.removeAttribute("aria-busy");
|
|
318
|
+
formElement.setAttribute("data-aigui-form-submitted", "");
|
|
319
|
+
for (const element of Array.from(formElement.elements)) if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element instanceof HTMLButtonElement || element instanceof HTMLFieldSetElement) element.disabled = true;
|
|
320
|
+
submit.textContent = options.submittedLabel ?? "Submitted";
|
|
321
|
+
};
|
|
322
|
+
if (submitted) markSubmitted();
|
|
311
323
|
const onInput = (event) => {
|
|
312
324
|
const control = event.target;
|
|
313
325
|
if (!(control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement || control instanceof HTMLSelectElement)) return;
|
|
314
326
|
clearFieldError(control.name, controls, errorElements);
|
|
315
327
|
actionError.hidden = true;
|
|
316
328
|
};
|
|
317
|
-
const onSubmit = (
|
|
318
|
-
|
|
319
|
-
if (pending || disposed) return;
|
|
329
|
+
const onSubmit = () => {
|
|
330
|
+
if (pending || submitted || disposed) return;
|
|
320
331
|
const validation = validateFormValues(definition, readControls(definition, controls));
|
|
321
332
|
renderErrors(validation.errors, controls, errorElements);
|
|
322
333
|
actionError.hidden = true;
|
|
@@ -333,14 +344,14 @@ function mountForm(host, definition, runtime) {
|
|
|
333
344
|
submit.disabled = false;
|
|
334
345
|
formElement.removeAttribute("aria-busy");
|
|
335
346
|
};
|
|
336
|
-
|
|
347
|
+
options.actionRuntime.dispatch({
|
|
337
348
|
type: definition.submitAction,
|
|
338
349
|
params: validation.values,
|
|
339
350
|
cardType
|
|
340
351
|
}, {
|
|
341
352
|
owner,
|
|
342
353
|
signal: controller.signal
|
|
343
|
-
}).then(
|
|
354
|
+
}).then(markSubmitted, (error) => {
|
|
344
355
|
if (!disposed && !controller.signal.aborted) {
|
|
345
356
|
actionError.textContent = actionErrorMessage(error);
|
|
346
357
|
actionError.hidden = false;
|
|
@@ -348,16 +359,19 @@ function mountForm(host, definition, runtime) {
|
|
|
348
359
|
settle();
|
|
349
360
|
});
|
|
350
361
|
};
|
|
362
|
+
const preventImplicitSubmit = (event) => event.preventDefault();
|
|
351
363
|
formElement.addEventListener("input", onInput);
|
|
352
364
|
formElement.addEventListener("change", onInput);
|
|
353
|
-
formElement.addEventListener("submit",
|
|
365
|
+
formElement.addEventListener("submit", preventImplicitSubmit);
|
|
366
|
+
submit.addEventListener("click", onSubmit);
|
|
354
367
|
host.replaceChildren(formElement);
|
|
355
368
|
return () => {
|
|
356
369
|
disposed = true;
|
|
357
370
|
controller.abort();
|
|
358
371
|
formElement.removeEventListener("input", onInput);
|
|
359
372
|
formElement.removeEventListener("change", onInput);
|
|
360
|
-
formElement.removeEventListener("submit",
|
|
373
|
+
formElement.removeEventListener("submit", preventImplicitSubmit);
|
|
374
|
+
submit.removeEventListener("click", onSubmit);
|
|
361
375
|
};
|
|
362
376
|
}
|
|
363
377
|
function createField(formId, field) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-gui/plugin-form",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Safe interactive form blocks for AIGUI, backed by the core ActionRuntime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ai-gui/core": "0.4.
|
|
52
|
+
"@ai-gui/core": "0.4.4"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsdown",
|