jsswift 1.0.49 → 1.0.50
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/AI.md +6 -0
- package/dist/documentions/AI.md +6 -0
- package/dist/documentions/components/dialog.md +4 -0
- package/dist/documentions/components/input.md +2 -0
- package/dist/documentions/components/select.md +2 -0
- package/dist/documentions/examples/crud.md +5 -2
- package/dist/documentions/knowledge-map.json +1 -1
- package/dist/jsswift.js +16 -3
- package/dist/jsswift.min.js +3 -3
- package/dist/min-jsswift.js +3 -3
- package/package.json +1 -1
package/AI.md
CHANGED
|
@@ -93,3 +93,9 @@ Run the app's build after implementation and check values, validation, dialog ke
|
|
|
93
93
|
- Other components: [index](components/README.md). MCP: read `guide:AI`, then search only for additional features.
|
|
94
94
|
|
|
95
95
|
For a complete application flow, read [the compact CRUD example](examples/crud.md) only when the task needs editing, validation, persistence and focus restoration together.
|
|
96
|
+
|
|
97
|
+
## Verification evidence and environment
|
|
98
|
+
|
|
99
|
+
Check whether browser tooling and server permissions are available before launching checks. Run Git commands only in a Git worktree; for example test `git rev-parse --is-inside-work-tree` before requesting a diff. Keep build, pure-logic tests and server startup results separate. An HTTP response confirms a reachable server, not that your startup command succeeded or that browser interactions work. Report environment failures separately from application failures. Test the implemented functions rather than rebuilding equivalent data inside an assertion.
|
|
100
|
+
|
|
101
|
+
Dialog autofocus preserves focus assigned inside `onOpen`; verify initial focus separately from validation focus and return focus. Field messages are associated with Input/Select through `aria-describedby` automatically.
|
package/dist/documentions/AI.md
CHANGED
|
@@ -93,3 +93,9 @@ Run the app's build after implementation and check values, validation, dialog ke
|
|
|
93
93
|
- Other components: [index](components/README.md). MCP: read `guide:AI`, then search only for additional features.
|
|
94
94
|
|
|
95
95
|
For a complete application flow, read [the compact CRUD example](examples/crud.md) only when the task needs editing, validation, persistence and focus restoration together.
|
|
96
|
+
|
|
97
|
+
## Verification evidence and environment
|
|
98
|
+
|
|
99
|
+
Check whether browser tooling and server permissions are available before launching checks. Run Git commands only in a Git worktree; for example test `git rev-parse --is-inside-work-tree` before requesting a diff. Keep build, pure-logic tests and server startup results separate. An HTTP response confirms a reachable server, not that your startup command succeeded or that browser interactions work. Report environment failures separately from application failures. Test the implemented functions rather than rebuilding equivalent data inside an assertion.
|
|
100
|
+
|
|
101
|
+
Dialog autofocus preserves focus assigned inside `onOpen`; verify initial focus separately from validation focus and return focus. Field messages are associated with Input/Select through `aria-describedby` automatically.
|
|
@@ -82,3 +82,7 @@ dialog.open();
|
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
Check Escape, Tab, focus restoration, and the accessible dialog name in the rendered application. Do not infer these checks from a successful build.
|
|
85
|
+
|
|
86
|
+
## Initial focus ordering
|
|
87
|
+
|
|
88
|
+
`onOpen(entry)` runs synchronously after mounting. Deferred autofocus selects the first control only when focus is outside the panel; focus assigned inside `onOpen` is preserved. Use `autoFocus: false` to disable automatic placement entirely. Closed overlays do not run pending autofocus.
|
|
@@ -471,3 +471,5 @@ Use the documented `model` binding for reactive state. Verify the rendered contr
|
|
|
471
471
|
`ariaLabel` sets the control's accessible name; a string `label` supplies the default. Use `ariaLabelledBy` for an external label ID and `ariaDescribedBy` for hint/error IDs. `ariaInvalid` overrides the invalid state; otherwise a truthy `error` marks the control invalid. `required` sets `aria-required` (and native required for Input). These props accept reactive values.
|
|
472
472
|
|
|
473
473
|
Input forwards `min`, `max`, `step`, `pattern`, `minLength` and `maxLength` to the native input, including zero values. Native constraints do not replace application validation.
|
|
474
|
+
|
|
475
|
+
Rendered field messages (error, warning, success, note or hint) receive a stable ID and are automatically associated with the interactive control through `aria-describedby`. External `ariaDescribedBy` IDs are preserved; the generated ID is removed when its message disappears.
|
|
@@ -480,3 +480,5 @@ Mount both controls; choose Casa through the menu, then save. The logged model v
|
|
|
480
480
|
`focus(options?)` on the returned field focuses the interactive input/combobox (it does not open Select). Prefer this method over private DOM references.
|
|
481
481
|
|
|
482
482
|
`ariaLabel` sets the control's accessible name; a string `label` supplies the default. Use `ariaLabelledBy` for an external label ID and `ariaDescribedBy` for hint/error IDs. `ariaInvalid` overrides the invalid state; otherwise a truthy `error` marks the control invalid. `required` sets `aria-required` (and native required for Input). These props accept reactive values.
|
|
483
|
+
|
|
484
|
+
Rendered field messages (error, warning, success, note or hint) receive a stable ID and are automatically associated with the interactive control through `aria-describedby`. External `ariaDescribedBy` IDs are preserved; the generated ID is removed when its message disappears.
|
|
@@ -6,6 +6,7 @@ Load JSswift and its UI CSS, then run the following code with an `#app` element.
|
|
|
6
6
|
const _ = window._;
|
|
7
7
|
const key = 'jsswift-crud-example';
|
|
8
8
|
const categories = ['Casa', 'Ufficio'];
|
|
9
|
+
const priceFormat = new Intl.NumberFormat('it-IT', {style:'currency', currency:'EUR'});
|
|
9
10
|
const initial = () => Array.from({length: 6}, (_, i) => ({id:i+1, name:`Prodotto ${i+1}`, category:categories[i%2], price:10, quantity:2}));
|
|
10
11
|
const valid = rows => Array.isArray(rows) && new Set(rows.map(r => r?.id)).size === rows.length && rows.every(r => r && Number.isSafeInteger(r.id) && r.id > 0 && typeof r.name === 'string' && r.name.trim() && categories.includes(r.category) && Number.isFinite(r.price) && r.price >= 0 && Number.isSafeInteger(r.quantity) && r.quantity >= 0);
|
|
11
12
|
let rows = initial(), editing = null, origin = 'add-product', query = '', category = '', page = 1;
|
|
@@ -50,7 +51,7 @@ function render() {
|
|
|
50
51
|
const found = rows.filter(r => r.name.toLowerCase().includes(query.trim().toLowerCase()) && (!category || r.category === category));
|
|
51
52
|
const pages = Math.max(1,Math.ceil(found.length/5)); page = Math.min(page,pages);
|
|
52
53
|
host.replaceChildren(_.Table({rows:found.slice((page-1)*5,page*5),rowKey:'id',pagination:false,hideFooter:true,minTableWidth:600,
|
|
53
|
-
columns:[{key:'name',label:'Nome'},{key:'category',label:'Categoria'},{key:'price',label:'Prezzo'},{key:'quantity',label:'Quantità'},
|
|
54
|
+
columns:[{key:'name',label:'Nome'},{key:'category',label:'Categoria'},{key:'price',label:'Prezzo',format:value => priceFormat.format(value)},{key:'quantity',label:'Quantità'},
|
|
54
55
|
{key:'actions',label:'Azioni',render:r => _.Button({id:`edit-${r.id}`,onClick:() => edit(r)},'Modifica')}] }));
|
|
55
56
|
pager.replaceChildren(_.span(`${found.length} prodotti — pagina ${page}/${pages}`),
|
|
56
57
|
_.Button({id:'previous',disabled:page===1,onClick:() => {page--;render();document.getElementById('previous').focus();}},'Precedente'),
|
|
@@ -59,4 +60,6 @@ function render() {
|
|
|
59
60
|
_.mount('#app', _.div({style:{minWidth:0}}, add, search, filter, host, pager, notice)); render();
|
|
60
61
|
```
|
|
61
62
|
|
|
62
|
-
Application validation remains explicit. `error` marks the control invalid;
|
|
63
|
+
Application validation remains explicit. `error` marks the control invalid; rendered field messages are associated automatically. Use `ariaDescribedBy` for additional application-owned descriptions. This example does not provide delete, server synchronization or a complete visual design.
|
|
64
|
+
|
|
65
|
+
Performance: the currency formatter is reused. This small example rebuilds its table for clarity; profile larger datasets before changing rendering strategy. `onOpen` focuses the name field and automatic focus respects that choice.
|
package/dist/jsswift.js
CHANGED
|
@@ -648,7 +648,10 @@
|
|
|
648
648
|
|
|
649
649
|
// scroll lock + focus
|
|
650
650
|
if (opts.lockScroll) lockScroll();
|
|
651
|
-
if (opts.autoFocus !== false) setTimeout(() =>
|
|
651
|
+
if (opts.autoFocus !== false) setTimeout(() => {
|
|
652
|
+
// Respect focus deliberately assigned by an onOpen callback.
|
|
653
|
+
if (panel.isConnected && stack.get(id) === entry && !panel.contains(document.activeElement)) focusFirst(panel);
|
|
654
|
+
}, 0);
|
|
652
655
|
|
|
653
656
|
// positioning (for menus/tooltips)
|
|
654
657
|
const position = (nextOpts = null) => {
|
|
@@ -7510,6 +7513,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
7510
7513
|
// props: { model: rod OR [get,set] signal OR plain, placeholder, type }
|
|
7511
7514
|
|
|
7512
7515
|
|
|
7516
|
+
let uiFieldMessageId = 0;
|
|
7513
7517
|
UI.FormField = (props = {}) => {
|
|
7514
7518
|
|
|
7515
7519
|
applyCommonProps(props);
|
|
@@ -7642,6 +7646,14 @@ const unitCover = (v, name = 'size') => {
|
|
|
7642
7646
|
return true;
|
|
7643
7647
|
};
|
|
7644
7648
|
let messageEl = null;
|
|
7649
|
+
const messageId = `jsswift-field-message-${++uiFieldMessageId}`;
|
|
7650
|
+
const syncDescription = () => {
|
|
7651
|
+
if (!controlEl?.setAttribute) return;
|
|
7652
|
+
const ids = [uiUnwrap(props.ariaDescribedBy), messageEl?.id].filter(Boolean).join(" ").trim();
|
|
7653
|
+
if (ids) controlEl.setAttribute("aria-describedby", [...new Set(ids.split(/\s+/))].join(" "));
|
|
7654
|
+
else controlEl.removeAttribute("aria-describedby");
|
|
7655
|
+
};
|
|
7656
|
+
wrap._syncDescription = syncDescription;
|
|
7645
7657
|
const renderMessage = () => {
|
|
7646
7658
|
const statusOrder = [
|
|
7647
7659
|
{ key: "error", value: resolveValue(props.error), slot: "errorMessage", className: "cms-error" },
|
|
@@ -7669,7 +7681,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
7669
7681
|
? renderSlotToArray(slots, active.slot, ctx, active.value)
|
|
7670
7682
|
: [];
|
|
7671
7683
|
const nextEl = nodes.length
|
|
7672
|
-
? _.div({ class: active?.className }, ...nodes)
|
|
7684
|
+
? _.div({ id: messageId, class: active?.className }, ...nodes)
|
|
7673
7685
|
: null;
|
|
7674
7686
|
|
|
7675
7687
|
const stateKeys = ["error", "warning", "success", "note"];
|
|
@@ -7682,6 +7694,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
7682
7694
|
wrap.appendChild(nextEl);
|
|
7683
7695
|
}
|
|
7684
7696
|
messageEl = nextEl;
|
|
7697
|
+
syncDescription();
|
|
7685
7698
|
};
|
|
7686
7699
|
|
|
7687
7700
|
// state updater
|
|
@@ -7834,7 +7847,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
7834
7847
|
const label = uiUnwrap(props.ariaLabel) ?? (typeof props.label === "string" ? props.label : null);
|
|
7835
7848
|
set("aria-label", label);
|
|
7836
7849
|
set("aria-labelledby", uiUnwrap(props.ariaLabelledBy));
|
|
7837
|
-
|
|
7850
|
+
field._syncDescription?.();
|
|
7838
7851
|
const invalid = props.ariaInvalid == null ? !!uiUnwrap(props.error) : !!uiUnwrap(props.ariaInvalid);
|
|
7839
7852
|
control.setAttribute("aria-invalid", String(invalid));
|
|
7840
7853
|
const required = !!uiUnwrap(props.required);
|