@lo-tek/interfaces 0.37.0 → 0.38.0

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.
Files changed (92) hide show
  1. package/CHANGELOG.md +137 -2
  2. package/README.md +4 -3
  3. package/agent/astro.md +1 -1
  4. package/agent/bundles.md +1 -1
  5. package/agent/datastar.md +48 -48
  6. package/agent/html.md +51 -51
  7. package/agent/react.md +1 -1
  8. package/agent/templates.md +49 -49
  9. package/core/lotek-core.wasm +0 -0
  10. package/core/lotek.wasm +0 -0
  11. package/dist/components/default-texts.d.ts +6 -0
  12. package/dist/components/default-texts.d.ts.map +1 -1
  13. package/dist/components/default-texts.js +6 -0
  14. package/dist/components/default-texts.js.map +1 -1
  15. package/dist/components/frame/dialog/lt-dialog.d.ts.map +1 -1
  16. package/dist/components/frame/dialog/lt-dialog.js +5 -2
  17. package/dist/components/frame/dialog/lt-dialog.js.map +1 -1
  18. package/dist/components/frame/error-summary/error-summary.d.ts +15 -2
  19. package/dist/components/frame/error-summary/error-summary.d.ts.map +1 -1
  20. package/dist/components/frame/error-summary/error-summary.js +33 -11
  21. package/dist/components/frame/error-summary/error-summary.js.map +1 -1
  22. package/dist/components/frame/error-summary/lt-error-summary.d.ts +11 -0
  23. package/dist/components/frame/error-summary/lt-error-summary.d.ts.map +1 -1
  24. package/dist/components/frame/error-summary/lt-error-summary.js +114 -8
  25. package/dist/components/frame/error-summary/lt-error-summary.js.map +1 -1
  26. package/dist/components/frame/field/lt-field.d.ts.map +1 -1
  27. package/dist/components/frame/field/lt-field.js +34 -14
  28. package/dist/components/frame/field/lt-field.js.map +1 -1
  29. package/dist/components/frame/popover/lt-popover.d.ts +6 -2
  30. package/dist/components/frame/popover/lt-popover.d.ts.map +1 -1
  31. package/dist/components/frame/popover/lt-popover.js +44 -14
  32. package/dist/components/frame/popover/lt-popover.js.map +1 -1
  33. package/dist/components/frame/session-timeout/lt-session-timeout.d.ts.map +1 -1
  34. package/dist/components/frame/session-timeout/lt-session-timeout.js +43 -6
  35. package/dist/components/frame/session-timeout/lt-session-timeout.js.map +1 -1
  36. package/dist/components/frame/suggestion/lt-suggestion.d.ts +8 -2
  37. package/dist/components/frame/suggestion/lt-suggestion.d.ts.map +1 -1
  38. package/dist/components/frame/suggestion/lt-suggestion.js +56 -37
  39. package/dist/components/frame/suggestion/lt-suggestion.js.map +1 -1
  40. package/dist/components/frame/tabs/lt-tabs.d.ts +32 -10
  41. package/dist/components/frame/tabs/lt-tabs.d.ts.map +1 -1
  42. package/dist/components/frame/tabs/lt-tabs.js +85 -21
  43. package/dist/components/frame/tabs/lt-tabs.js.map +1 -1
  44. package/dist/components/host-element.d.ts +49 -0
  45. package/dist/components/host-element.d.ts.map +1 -1
  46. package/dist/components/host-element.js +74 -0
  47. package/dist/components/host-element.js.map +1 -1
  48. package/dist/components/standalone/connection-status/connection-status.d.ts +5 -1
  49. package/dist/components/standalone/connection-status/connection-status.d.ts.map +1 -1
  50. package/dist/components/standalone/connection-status/connection-status.js +5 -1
  51. package/dist/components/standalone/connection-status/connection-status.js.map +1 -1
  52. package/dist/components/standalone/connection-status/lt-connection-status.d.ts +11 -6
  53. package/dist/components/standalone/connection-status/lt-connection-status.d.ts.map +1 -1
  54. package/dist/components/standalone/connection-status/lt-connection-status.js +34 -10
  55. package/dist/components/standalone/connection-status/lt-connection-status.js.map +1 -1
  56. package/dist/diagnostics/core.d.ts +34 -0
  57. package/dist/diagnostics/core.d.ts.map +1 -1
  58. package/dist/diagnostics/core.js +14 -2
  59. package/dist/diagnostics/core.js.map +1 -1
  60. package/dist/lotek.css +1 -1
  61. package/dist/lt.d.ts +1 -1
  62. package/dist/react.d.ts +2 -0
  63. package/dist/react.d.ts.map +1 -1
  64. package/dist/vocabulary/classes.d.ts.map +1 -1
  65. package/dist/vocabulary/classes.js +34 -0
  66. package/dist/vocabulary/classes.js.map +1 -1
  67. package/dist/vocabulary/elements.d.ts.map +1 -1
  68. package/dist/vocabulary/elements.js +87 -7
  69. package/dist/vocabulary/elements.js.map +1 -1
  70. package/dist/vocabulary/types.d.ts +54 -0
  71. package/dist/vocabulary/types.d.ts.map +1 -1
  72. package/live-templates.xml +3 -2
  73. package/manifest/manifest.json +133 -8
  74. package/manifest/manifest.schema.json +73 -0
  75. package/package.json +2 -1
  76. package/src/components/default-texts.ts +7 -0
  77. package/src/components/frame/dialog/lt-dialog.ts +5 -2
  78. package/src/components/frame/error-summary/error-summary.ts +45 -11
  79. package/src/components/frame/error-summary/lt-error-summary.ts +121 -7
  80. package/src/components/frame/field/lt-field.ts +65 -28
  81. package/src/components/frame/popover/lt-popover.ts +43 -12
  82. package/src/components/frame/session-timeout/lt-session-timeout.ts +40 -6
  83. package/src/components/frame/suggestion/lt-suggestion.ts +57 -36
  84. package/src/components/frame/tabs/lt-tabs.ts +114 -38
  85. package/src/components/host-element.ts +93 -0
  86. package/src/components/standalone/connection-status/connection-status.ts +7 -1
  87. package/src/components/standalone/connection-status/lt-connection-status.ts +36 -10
  88. package/src/diagnostics/core.ts +19 -2
  89. package/src/vocabulary/classes.ts +34 -0
  90. package/src/vocabulary/elements.ts +87 -7
  91. package/src/vocabulary/types.ts +50 -0
  92. package/web-types.json +1 -1
@@ -12,6 +12,8 @@ export type ConnectionStatusOptions = {
12
12
 
13
13
  export type ConnectionStatusAttributes = {
14
14
  class: typeof CONNECTION_STATUS_CLASS
15
+ role: "status"
16
+ "aria-live": "polite"
15
17
  "offline-text"?: string
16
18
  "online-text"?: string
17
19
  "data-ignore-morph": ""
@@ -23,7 +25,9 @@ export type ConnectionStatusAttributes = {
23
25
  * By definition the server cannot tell you it is unreachable. When it is
24
26
  * down, nothing comes from it to render. So the component creates all the
25
27
  * content itself, and `data-ignore-morph` stops a later patch from tearing
26
- * it away the moment the connection is back.
28
+ * it away the moment the connection is back. The host is the live region,
29
+ * `role="status"` with `aria-live="polite"`, so the region exists before the
30
+ * bar and its text arrive.
27
31
  *
28
32
  * ```ts
29
33
  * <lt-connection-status {...lt.connectionStatus()} />
@@ -35,6 +39,8 @@ export const connectionStatus = ({
35
39
  }: ConnectionStatusOptions = {}): ConnectionStatusAttributes =>
36
40
  attributes({
37
41
  class: CONNECTION_STATUS_CLASS,
42
+ role: "status" as const,
43
+ "aria-live": "polite" as const,
38
44
  "offline-text": offlineText,
39
45
  "online-text": onlineText,
40
46
  "data-ignore-morph": "" as const,
@@ -77,6 +77,23 @@ export class LtConnectionStatus extends HostElement {
77
77
 
78
78
  connectedCallback(): void {
79
79
  this.classList.add(CONNECTION_STATUS_CLASS)
80
+ /*
81
+ * The host is the live region, from the moment it is connected, so the
82
+ * region exists long before the bar and its text arrive. Only what is
83
+ * missing: a role or politeness the server wrote stays. When the bar was
84
+ * the region, it was inserted together with its text, and Firefox sent
85
+ * no live-region text through AT-SPI at all, so "No connection" was
86
+ * never announced. `check-accessibility-tree.ts` checks it.
87
+ */
88
+ this.markRegion()
89
+ // A morph without `data-ignore-morph` takes both off, and then the next
90
+ // outage is never announced. Writing only what is missing keeps the
91
+ // observer from triggering itself.
92
+ this.regionObserver = new MutationObserver(() => this.markRegion())
93
+ this.regionObserver.observe(this, {
94
+ attributes: true,
95
+ attributeFilter: ["role", "aria-live"],
96
+ })
80
97
  window.addEventListener("offline", this.handleOffline)
81
98
  window.addEventListener("online", this.handleOnline)
82
99
  if (!navigator.onLine) {
@@ -96,7 +113,22 @@ export class LtConnectionStatus extends HostElement {
96
113
  }
97
114
  }
98
115
 
116
+ private regionObserver?: MutationObserver
117
+
118
+ /**
119
+ * The region, as a pair and only when both are missing. A role the server
120
+ * wrote carries its own politeness (`alert` is assertive), and an
121
+ * `aria-live` added next to it would override that.
122
+ */
123
+ private markRegion(): void {
124
+ if (this.hasAttribute("role") || this.hasAttribute("aria-live")) return
125
+ this.setAttribute("role", "status")
126
+ this.setAttribute("aria-live", "polite")
127
+ }
128
+
99
129
  disconnectedCallback(): void {
130
+ this.regionObserver?.disconnect()
131
+ this.regionObserver = undefined
100
132
  window.removeEventListener("offline", this.handleOffline)
101
133
  window.removeEventListener("online", this.handleOnline)
102
134
  if (this.timer) window.clearTimeout(this.timer)
@@ -107,12 +139,10 @@ export class LtConnectionStatus extends HostElement {
107
139
  * The bar, created empty first and filled afterwards.
108
140
  *
109
141
  * A live region must exist before its content arrives, or assistive
110
- * technology does not announce the first change. When the bar was already
111
- * filled as it was inserted, the region appeared complete to the screen
112
- * reader, and "No connection", the most important message, could be lost.
113
- * So the text is set in the next frame when the bar is new. This cannot be
114
- * checked without a screen reader; the rule is known from NVDA, JAWS and
115
- * VoiceOver.
142
+ * technology does not announce the first change. The region is the host
143
+ * (see `connectedCallback`), so the bar is content inside a region that is
144
+ * already there; the text is still set in the next frame when the bar is
145
+ * new, so the bar and its text arrive as two changes.
116
146
  *
117
147
  * The bar is created only when it is needed, not on connection: when it
118
148
  * was in the DOM before React hydrated, React found an element it had not
@@ -123,10 +153,6 @@ export class LtConnectionStatus extends HostElement {
123
153
 
124
154
  const bar = document.createElement("div")
125
155
  bar.className = CONNECTION_STATUS_BAR_CLASS
126
- // polite and not assertive: the message matters, but should not
127
- // interrupt mid-word. It stays until the connection is back.
128
- bar.setAttribute("role", "status")
129
- bar.setAttribute("aria-live", "polite")
130
156
  this.append(bar)
131
157
  this.bar = bar
132
158
  return { bar, fresh: true }
@@ -148,12 +148,23 @@ type Exports = {
148
148
  inspect_theme_raw(pointer: number, length: number): number
149
149
  inspect_styles_raw(pointer: number, length: number): void
150
150
  diagnose_styled_raw(pointer: number, length: number): number
151
+ accessibility_tree_raw(pointer: number, length: number): number
152
+ accessibility_facts_raw(): void
153
+ accessibility_roles_raw(): void
154
+ accessibility_states_raw(): void
151
155
  result_ptr(): number
152
156
  result_len(): number
153
157
  }
154
158
 
155
- /** Loads the core from the module's bytes or an already compiled module. */
156
- export function loadCore(source: BufferSource | WebAssembly.Module): Core {
159
+ /**
160
+ * @internal Not part of the package's API.
161
+ *
162
+ * An instance of the core and the helpers every call goes through: the text
163
+ * written into the module's memory, and the answer read back. Not part of
164
+ * the package: `index.ts` exports `loadCore`. The tests reach the core's
165
+ * other functions, such as the accessibility tree, through this.
166
+ */
167
+ export function instantiate(source: BufferSource | WebAssembly.Module) {
157
168
  const module =
158
169
  source instanceof WebAssembly.Module
159
170
  ? source
@@ -181,6 +192,12 @@ export function loadCore(source: BufferSource | WebAssembly.Module): Core {
181
192
  if (status !== 0) throw new Error(read<{ error: string }>().error)
182
193
  return read<T>()
183
194
  }
195
+ return { e, write, read, answer }
196
+ }
197
+
198
+ /** Loads the core from the module's bytes or an already compiled module. */
199
+ export function loadCore(source: BufferSource | WebAssembly.Module): Core {
200
+ const { e, write, read, answer } = instantiate(source)
184
201
 
185
202
  return {
186
203
  diagnoseMarkup: (html) => answer(e.diagnose_markup_raw(...write(html))),
@@ -311,6 +311,7 @@ export const classes: Classes = {
311
311
  "optional": true
312
312
  }
313
313
  },
314
+ "name": {},
314
315
  "attributes": {
315
316
  "data-state": {
316
317
  "values": [
@@ -328,6 +329,22 @@ export const classes: Classes = {
328
329
  "link": "https://lo-tek.sobernetics.no/components/fieldset/",
329
330
  "content": "text",
330
331
  "partOf": "lt-fieldset",
332
+ "texts": [
333
+ {
334
+ "variable": "--lt-label-required-text",
335
+ "text": " (required)",
336
+ "attribute": "data-required",
337
+ "value": "text"
338
+ },
339
+ {
340
+ "variable": "--lt-label-optional-text",
341
+ "text": " (optional)",
342
+ "attribute": "data-optional",
343
+ "unless": [
344
+ "data-required"
345
+ ]
346
+ }
347
+ ],
331
348
  "attributes": {
332
349
  "data-required": {
333
350
  "values": [
@@ -453,6 +470,22 @@ export const classes: Classes = {
453
470
  "description": "A label for form fields, with marking of required and optional. A CSS component.",
454
471
  "link": "https://lo-tek.sobernetics.no/components/label/",
455
472
  "content": "text",
473
+ "texts": [
474
+ {
475
+ "variable": "--lt-label-required-text",
476
+ "text": " (required)",
477
+ "attribute": "data-required",
478
+ "value": "text"
479
+ },
480
+ {
481
+ "variable": "--lt-label-optional-text",
482
+ "text": " (optional)",
483
+ "attribute": "data-optional",
484
+ "unless": [
485
+ "data-required"
486
+ ]
487
+ }
488
+ ],
456
489
  "attributes": {
457
490
  "data-required": {
458
491
  "values": [
@@ -885,6 +918,7 @@ export const classes: Classes = {
885
918
  "multiple": true
886
919
  }
887
920
  },
921
+ "name": {},
888
922
  "attributes": {}
889
923
  },
890
924
  "lt-toggle-group__option": {
@@ -30,7 +30,10 @@ export const elements: Elements = {
30
30
  "description": "The control the field wires: an `<input>`, `<textarea>` or `<select>` with its `<option>`s. `type=\"hidden\"` does not count, and only the first one is wired. The template writes `for` on the label and `id` on the control, or wraps the control in the label.",
31
31
  "content": "none",
32
32
  "unique": true,
33
- "stableId": true
33
+ "stableId": true,
34
+ "name": {
35
+ "after": "label"
36
+ }
34
37
  },
35
38
  "help": {
36
39
  "selector": ".lt-help-text",
@@ -75,7 +78,8 @@ export const elements: Elements = {
75
78
  "selector": "[role='tablist'], .lt-tabs__list",
76
79
  "description": "The tab list, a `<ul>` where each `<li>` gets `role=\"presentation\"`. It needs a name, such as `aria-label`. When the server has written `role=\"tab\"` on the links, the list can also hold other links.",
77
80
  "content": "free",
78
- "unique": true
81
+ "unique": true,
82
+ "name": {}
79
83
  },
80
84
  "tab": {
81
85
  "selector": "a[role='tab'][href^='#'], .lt-tabs__list a[href^='#']:not([role])",
@@ -87,7 +91,10 @@ export const elements: Elements = {
87
91
  "selector": "[role='tabpanel'], .lt-tabs__panel:not([role])",
88
92
  "description": "The content of one tab, with the id the link points to. The panel has the same name as the tab: the heading at the top of the panel is the name when it says the same as the tab.",
89
93
  "content": "free",
90
- "multiple": true
94
+ "multiple": true,
95
+ "name": {
96
+ "component": true
97
+ }
91
98
  }
92
99
  },
93
100
  "events": {
@@ -127,6 +134,10 @@ export const elements: Elements = {
127
134
  "content": "text",
128
135
  "multiple": true
129
136
  }
137
+ },
138
+ "name": {
139
+ "after": "title",
140
+ "id": true
130
141
  }
131
142
  },
132
143
  "lt-popover": {
@@ -220,13 +231,19 @@ export const elements: Elements = {
220
231
  "description": "The field the user types in: `role=\"combobox\"`, otherwise the first `<input>`. The template writes `for` on the label and `id` on the field, or wraps the field in the label.",
221
232
  "content": "none",
222
233
  "unique": true,
223
- "stableId": true
234
+ "stableId": true,
235
+ "name": {
236
+ "after": "label"
237
+ }
224
238
  },
225
239
  "list": {
226
240
  "selector": "[role='listbox'], .lt-suggestion__list",
227
241
  "description": "The list of suggestions. It sits in the top layer when it is open, `popover=\"manual\"`, and has the label as its name.",
228
242
  "content": "parts",
229
- "unique": true
243
+ "unique": true,
244
+ "name": {
245
+ "component": true
246
+ }
230
247
  },
231
248
  "option": {
232
249
  "selector": "[role='option'], .lt-suggestion__list > li:not([role])",
@@ -252,6 +269,34 @@ export const elements: Elements = {
252
269
  "content": "none",
253
270
  "unique": true,
254
271
  "optional": true
272
+ },
273
+ "label": {
274
+ "selector": "label",
275
+ "description": "The label. The component writes `for` on it from the field's id when it does not wrap the field, and names the list after it.",
276
+ "content": "text",
277
+ "optional": true
278
+ }
279
+ },
280
+ "texts": {
281
+ "suggestionNone": {
282
+ "text": "No matches",
283
+ "replacedBy": [
284
+ "count-none",
285
+ "count-other"
286
+ ]
287
+ },
288
+ "suggestionOne": {
289
+ "text": "One match",
290
+ "replacedBy": [
291
+ "count-one",
292
+ "count-other"
293
+ ]
294
+ },
295
+ "suggestionOther": {
296
+ "text": "{n} matches",
297
+ "replacedBy": [
298
+ "count-other"
299
+ ]
255
300
  }
256
301
  },
257
302
  "events": {
@@ -281,7 +326,10 @@ export const elements: Elements = {
281
326
  "selector": ":scope > dialog",
282
327
  "description": "The `<dialog>` element, as a direct child of the host.",
283
328
  "content": "free",
284
- "unique": true
329
+ "unique": true,
330
+ "name": {
331
+ "after": "title"
332
+ }
285
333
  },
286
334
  "title": {
287
335
  "selector": "h1, h2, h3, h4, h5, h6",
@@ -324,6 +372,21 @@ export const elements: Elements = {
324
372
  }
325
373
  },
326
374
  "parts": {},
375
+ "texts": {
376
+ "toastRegion": {
377
+ "text": "Notifications",
378
+ "replacedBy": [
379
+ "label",
380
+ "aria-label"
381
+ ]
382
+ },
383
+ "toastClose": {
384
+ "text": "Close message",
385
+ "replacedBy": [
386
+ "close-label"
387
+ ]
388
+ }
389
+ },
327
390
  "events": {
328
391
  "toast-dismiss": {}
329
392
  }
@@ -350,7 +413,10 @@ export const elements: Elements = {
350
413
  "selector": "dialog",
351
414
  "description": "The `<dialog>` element with the warning.",
352
415
  "content": "free",
353
- "unique": true
416
+ "unique": true,
417
+ "name": {
418
+ "after": "title"
419
+ }
354
420
  },
355
421
  "title": {
356
422
  "selector": "h1, h2, h3, h4, h5, h6",
@@ -404,6 +470,20 @@ export const elements: Elements = {
404
470
  }
405
471
  },
406
472
  "parts": {},
473
+ "texts": {
474
+ "connectionOffline": {
475
+ "text": "No connection. What you type is not being saved.",
476
+ "replacedBy": [
477
+ "offline-text"
478
+ ]
479
+ },
480
+ "connectionOnline": {
481
+ "text": "Connection restored.",
482
+ "replacedBy": [
483
+ "online-text"
484
+ ]
485
+ }
486
+ },
407
487
  "events": {
408
488
  "connection-lost": {},
409
489
  "connection-restored": {}
@@ -34,8 +34,29 @@ export type Part = {
34
34
  optional?: true
35
35
  /** The part must have an id from the server; the component never writes one. */
36
36
  stableId?: true
37
+ /** LO-TEK demands a name for the part. */
38
+ name?: Name
37
39
  }
38
40
 
41
+ /**
42
+ * A name LO-TEK demands where WAI-ARIA does not, or one the component gives
43
+ * itself. WAI-ARIA's own demands, by role, are in the core, and `lotek check`
44
+ * reports an element the markup gives no name (`without-name`).
45
+ *
46
+ * `after` is the part the component points `aria-labelledby` at when the
47
+ * markup has neither `aria-label` nor `aria-labelledby`, such as the heading
48
+ * of a dialog; then that part's name is enough. With `id` the part must have
49
+ * an id from the template, since the component does not write one. With
50
+ * `component` the component names it itself, after what the other rules
51
+ * check, such as the list in a suggestion field after the label, and no
52
+ * rule judges its name in the markup.
53
+ *
54
+ * Each of them is held to the component by a contract in
55
+ * `interfaces/scripts/accessibility/contracts.ts`: a mutation that takes the name away
56
+ * must fail in the browsers, and `lotek check` must report it.
57
+ */
58
+ export type Name = { after?: string; id?: true; component?: true }
59
+
39
60
  /**
40
61
  * An event an element dispatches, such as `dialog-toggle`. `detail` is the
41
62
  * fields in `event.detail` with their types, and is absent when the event
@@ -52,10 +73,35 @@ export type Element = {
52
73
  /** Empty for the elements that create all of their content themselves. */
53
74
  parts?: Record<string, Part>
54
75
  events?: Record<string, ElementEvent>
76
+ /** LO-TEK demands a name for the element itself. */
77
+ name?: Name
78
+ /** The texts the element writes itself, by their name in `DEFAULT_TEXTS`. */
79
+ texts?: Record<string, DefaultText>
55
80
  /** Attributes that have been removed, with what applies instead. */
56
81
  removed?: Record<string, string>
57
82
  }
58
83
 
84
+ /**
85
+ * A text the element writes itself when nobody has given it another, from
86
+ * `DEFAULT_TEXTS`. `replacedBy` is the attributes that replace it; any one
87
+ * of them is enough, and `lotek check` warns when none is there on a page in
88
+ * another language than the text's.
89
+ */
90
+ export type DefaultText = { text: string; replacedBy: readonly string[] }
91
+
92
+ /**
93
+ * A text the stylesheet shows in `::after` on an element with the class and
94
+ * `attribute`, with `value` when the selector requires one, and none of the
95
+ * attributes in `unless`. `variable` is the CSS variable that replaces it.
96
+ */
97
+ export type CssText = {
98
+ variable: string
99
+ text: string
100
+ attribute: string
101
+ value?: string
102
+ unless?: readonly string[]
103
+ }
104
+
59
105
  /** The content of `elements.ts`: tag to element. */
60
106
  export type Elements = Record<string, Element>
61
107
 
@@ -88,6 +134,10 @@ export type ClassInfo = {
88
134
  partOf?: string
89
135
  /** The parts the markup consists of, for the classes with a fixed structure. */
90
136
  parts?: Record<string, Part>
137
+ /** LO-TEK demands a name for the element with the class. */
138
+ name?: Name
139
+ /** The texts the stylesheet shows on it. */
140
+ texts?: readonly CssText[]
91
141
  attributes: Record<string, ClassAttribute>
92
142
  }
93
143
 
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "@lo-tek/interfaces",
4
- "version": "0.37.0",
4
+ "version": "0.38.0",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {