jsswift 1.0.49 → 1.0.51

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 CHANGED
@@ -93,3 +93,22 @@ 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.
102
+
103
+ ## Focus CSS and targeted reads
104
+
105
+ Apply focus indicators to every selector through `:is`, rather than placing the pseudo-class on only the final selector in a comma-separated list:
106
+
107
+ ```css
108
+ :is(button, a, input, select, [tabindex]):focus-visible {
109
+ outline: 3px solid #2563eb;
110
+ outline-offset: 3px;
111
+ }
112
+ ```
113
+
114
+ When MCP is configured, request missing component details with `jsswift_read({id:"component:table", section:"props", keys:["pagination","hideFooter"]})`. `section` supports props, events, methods and slots; omit it for the full document. Inherited props are resolved automatically; unknown keys return an error. This reduces returned text, not the API knowledge needed by the task.
@@ -93,3 +93,22 @@ 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.
102
+
103
+ ## Focus CSS and targeted reads
104
+
105
+ Apply focus indicators to every selector through `:is`, rather than placing the pseudo-class on only the final selector in a comma-separated list:
106
+
107
+ ```css
108
+ :is(button, a, input, select, [tabindex]):focus-visible {
109
+ outline: 3px solid #2563eb;
110
+ outline-offset: 3px;
111
+ }
112
+ ```
113
+
114
+ When MCP is configured, request missing component details with `jsswift_read({id:"component:table", section:"props", keys:["pagination","hideFooter"]})`. `section` supports props, events, methods and slots; omit it for the full document. Inherited props are resolved automatically; unknown keys return an error. This reduces returned text, not the API knowledge needed by the task.
@@ -68,3 +68,7 @@ npm run test:knowledge
68
68
  `knowledge:check` verifies document references, component coverage, names, and metadata descriptions. `knowledge:refresh` rebuilds component entries from their Markdown metadata, preserving curated keywords and topic assignments. Maintain topic paths and keywords in `knowledge-map.json`; review routing when adding a new component, then regenerate the portable Skill. Restart the MCP after updating documentation so its map is reloaded.
69
69
 
70
70
  For framework maintenance use [docDevelop/README.md](../docDevelop/README.md).
71
+
72
+ ## Targeted component reads
73
+
74
+ After search, use `jsswift_read` with `id`, optional `section` (props/events/methods/slots), and optional `keys`. Keys require a section; only documented keys are accepted. Props inherited from common-props are resolved. Existing full-document reads and resources remain available. This interface must be configured in the client; it does not change benchmarks without MCP access.
@@ -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.
@@ -91,3 +91,9 @@ _.Table({
91
91
  ```
92
92
 
93
93
  Use the documented visibility and column style props before hiding or restyling internal table classes. With external pagination, pass the current page's rows and disable internal pagination.
94
+
95
+ ## External filters and pagination
96
+
97
+ For application-owned filtering/paging, pass the already filtered page as `rows`, set `pagination: false` and `hideFooter: true`. `minTableWidth` keeps columns readable in a horizontally scrollable container. Provide counters and pager buttons outside Table.
98
+
99
+ `toolbarStart`, `toolbar` and `toolbarEnd` add content to the optional toolbar. The built-in row-count status is a separate region and remains visible even with `hideFooter: true`; it counts the rows supplied to Table, not the full application inventory. The current contract has no `hideStatus` prop. Do not invent one or confuse `hideHeader` (column headings) with the toolbar/status. Table has its own Card wrapper; nesting it in another Card may produce an additional frame. Customize using supported props and theme values first.
@@ -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; use `ariaDescribedBy` when associating an application-owned error message. This example does not provide delete, server synchronization or a complete visual design.
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.
@@ -983,5 +983,5 @@
983
983
  "documentAliases": {
984
984
  "components/button.md": "components/btn.md"
985
985
  },
986
- "packageVersion": "1.0.49"
986
+ "packageVersion": "1.0.51"
987
987
  }
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(() => focusFirst(panel), 0);
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
- set("aria-describedby", uiUnwrap(props.ariaDescribedBy));
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);