@ntix/components-scorad 1.0.2 → 1.0.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.
Files changed (44) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +28 -0
  3. package/VALIDATION.md +39 -0
  4. package/dist/@ntix/components-scorad.es.js +107 -46
  5. package/dist/@ntix/components-scorad.umd.js +19 -19
  6. package/dist/HTMLScoradValueElement.d.ts +37 -0
  7. package/dist/ScoradData.d.ts +1 -1
  8. package/dist/ScoradExtentData.d.ts +11 -7
  9. package/dist/ScoradExtentWeights.d.ts +1 -1
  10. package/dist/ScoradIntensityData.d.ts +11 -7
  11. package/dist/ScoradResources.d.ts +11 -0
  12. package/dist/ScoradScore.d.ts +14 -1
  13. package/dist/ScoradSubjectiveData.d.ts +7 -3
  14. package/dist/component.d.ts +36 -6
  15. package/dist/componentsScoradLogger.d.ts +8 -0
  16. package/dist/constants.d.ts +19 -4
  17. package/dist/extent/component.d.ts +50 -7
  18. package/dist/getScoradScore.d.ts +1 -2
  19. package/dist/index.d.ts +1 -0
  20. package/dist/intensity/component.d.ts +40 -7
  21. package/dist/label/component.d.ts +1 -1
  22. package/dist/options/component.d.ts +8 -11
  23. package/dist/subjective/component.d.ts +40 -7
  24. package/dist/weightings/component.d.ts +30 -7
  25. package/docs/HTMLScoradValueElement.md +35 -0
  26. package/docs/README.md +36 -0
  27. package/docs/ScoradData.md +13 -0
  28. package/docs/ScoradExtentData.md +63 -0
  29. package/docs/ScoradExtentWeights.md +13 -0
  30. package/docs/ScoradIntensityData.md +64 -0
  31. package/docs/ScoradResources.md +41 -0
  32. package/docs/ScoradScore.md +47 -0
  33. package/docs/ScoradSubjectiveData.md +32 -0
  34. package/docs/component.md +68 -0
  35. package/docs/componentsScoradLogger.md +14 -0
  36. package/docs/constants.md +99 -0
  37. package/docs/extent.component.md +86 -0
  38. package/docs/getScoradScore.md +25 -0
  39. package/docs/intensity.component.md +77 -0
  40. package/docs/selectScoradWeights.md +25 -0
  41. package/docs/subjective.component.md +77 -0
  42. package/docs/validateScoradData.md +26 -0
  43. package/docs/weightings.component.md +59 -0
  44. package/package.json +15 -7
package/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- # @ntix/components-scorad
1
+ # License: @ntix/components-scorad
2
2
 
3
3
  Copyright (c) 2025 Antix Software Limited ([antix.co.uk](https://antix.co.uk))
4
4
 
package/README.md CHANGED
@@ -2,12 +2,18 @@
2
2
 
3
3
  A professional-grade component library for calculating and visualizing SCORAD (SCORing Atopic Dermatitis) assessments. Developed by Antix Software Limited.
4
4
 
5
+ **View the interactive demo [antix.co.uk/scorad](https://antix.co.uk/scorad)**
6
+
5
7
  ## Features
6
8
 
7
9
  - Interactive Body Maps: High-performance SVG-based surface area selection.
8
10
  - SCORAD Formula: Sections A (Extent), B (Intensity), and C (Subjective).
9
11
  - Custom Elements: Easy integration with any framework or plain HTML.
10
12
 
13
+ ## Validation & Accuracy
14
+ This component has been mathematically validated against the ETFAD SCORAD reference standards.
15
+ See [VALIDATION.md](./VALIDATION.md) for the full report and test coverage details.
16
+
11
17
  ## Installation
12
18
 
13
19
  ```shell
@@ -43,6 +49,28 @@ Then add the component to your HTML:
43
49
  <scorad-component></scorad-component>
44
50
  ```
45
51
 
52
+ ## Documentation
53
+
54
+ ### 🧱 Components
55
+ *Primary UI elements for building the assessment.*
56
+
57
+ - [**Scorad Root**](./docs/component.md) – The main coordinator and score aggregator.
58
+ - [**Extent (Section A)**](./docs/extent.component.md) – Surface area calculation.
59
+ - [**Intensity (Section B)**](./docs/intensity.component.md) – Clinical sign grading.
60
+ - [**Subjective (Section C)**](./docs/subjective.component.md) – Patient-reported symptoms.
61
+ - [**Weightings**](./docs/weightings.component.md) – Adult/Child toggle.
62
+
63
+ ### 📊 Data Models & Logic
64
+ *Records and calculation engines.*
65
+
66
+ - [**ScoradScore**](./docs/ScoradScore.md) – The output structure (A, B, C, Total).
67
+ - [**ScoradData**](./docs/ScoradData.md) – The complete assessment state.
68
+ - [**getScoradScore**](./docs/getScoradScore.md) – The mathematical formula implementation.
69
+ - [**Clinical Constants**](./docs/constants.md) – Max values and defaults.
70
+
71
+ ### 🌐 Resources
72
+ - [**Localization (ScoradResources)**](./docs/ScoradResources.md) – UI strings and descriptions.
73
+
46
74
  ## Licensing
47
75
 
48
76
  This package is NOT open-source. Usage is governed by the Antix Software License.
package/VALIDATION.md ADDED
@@ -0,0 +1,39 @@
1
+ # Validation: @ntix/components-scorad
2
+
3
+ ## 1. Overview
4
+ This document outlines the validation procedures and mathematical accuracy of the SCORAD (SCORing Atopic Dermatitis) calculation logic within this package.
5
+
6
+ ## 2. Reference Standard
7
+ The implementation is based on the consensus report of the European Task Force on Atopic Dermatitis (ETFAD).
8
+
9
+ **Formula:** $Score = \frac{A}{5} + \frac{7B}{2} + C$
10
+
11
+ - **A (Extent):** Surface area percentage (0–100). Includes adult/child weighting adjustments for head and limbs.
12
+ - **B (Intensity):** Sum of 6 clinical signs (0–3 each), max 18.
13
+ - **C (Subjective):** Sum of Pruritus and Insomnia (0–10 each), max 20.
14
+
15
+ ## 3. Automated Test Suite
16
+ Validation is performed using `vitest` in a `happy-dom` environment. The following critical paths are verified on every build:
17
+
18
+ ### 3.1. Boundary Testing
19
+ - **Minimum Score:** Confirmed $0.00$ when all inputs are zero.
20
+ - **Maximum Score:** Confirmed $103.00$ when all inputs are at maximum capacity.
21
+
22
+ ### 3.2. Weighting & Precision
23
+ - **Intensity Weighting:** Verifies that Intensity (Section B) contributes to the total with a multiplier of $3.5$ (e.g., an Intensity of 3 results in a sub-score of 10.5).
24
+ - **Rounding:** Results are rounded to 2 decimal places to ensure consistency across clinical documentation.
25
+
26
+ ### 3.3. Age-Based Extent (Adult vs Child)
27
+ - Validates the calculation across both adult and pediatric surface area distributions.
28
+
29
+ ## 4. Error Handling & Safety
30
+ The library implements strict validation via `@ntix/components-core`. Calculations will fail with a descriptive `Failure` object if:
31
+ - Extent values exceed physiological maximums.
32
+ - Intensity values fall outside the 0–3 range.
33
+ - Subjective values fall outside the 0–10 range.
34
+
35
+ ## 5. Summary
36
+ The @ntix/components-scorad calculation logic is deemed mathematically sound and compliant with the ETFAD standard for digital SCORAD assessments.
37
+
38
+ ---
39
+ © 2025 Antix Software Limited ([antix.co.uk](https://antix.co.uk)). Prepared for clinical and commercial review.
@@ -967,7 +967,7 @@ const SCORAD_EXTENT_MAX_VALUE = 100, SCORAD_INTENSITY_MAX_VALUE = 3, SCORAD_SUBJ
967
967
  lowerLimbs: .28,
968
968
  genitals: 0
969
969
  };
970
- var component_default$6 = ":host{--host-display:inline-flex;--host-font-size:1rem;flex-wrap:wrap;justify-content:center;align-items:center;position:relative}label{padding:var(--host-padding-unit)}svg{min-width:14rem;max-width:18rem;margin:var(--host-padding-unit)0;fill:#0000;stroke:var(--host-color);stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;flex:16rem;overflow:visible}@media print{svg{flex:0 0 14rem;width:14rem}}path,line{vector-effect:non-scaling-stroke}path{opacity:.333}text,line{opacity:.667}g{outline:none}g[tabindex] path{opacity:1;fill:hsla(var(--scorad-h,0),var(--extent,\"0%\"),50%,var(--opacity,0));cursor:grab}:has(:active) g[tabindex] path{cursor:ns-resize}:is(:not(:has(:active)) g[tabindex]:hover path,:not(:has(:active)) g[tabindex]:hover line){stroke-width:2px;stroke:var(--host-primary-color);stroke-dasharray:2 3.5;opacity:1}g[tabindex]:focus path,g[tabindex]:active path,g[tabindex].selected path,g[tabindex]:focus line,g[tabindex]:active line,g[tabindex].selected line{stroke-width:2px;stroke:var(--host-primary-color);opacity:1;stroke-dasharray:none!important}g[tabindex] text{fill:var(--host-color);stroke:none;font-size:var(--host-font-size);text-anchor:end}g[tabindex].posterior-trunk text{text-anchor:start}:is(:not(:has(:active)) g[tabindex]:hover text,g[tabindex]:focus text,g[tabindex]:active text,g[tabindex].selected text){fill:var(--host-primary-color);opacity:1}";
970
+ var component_default$6 = ":host{--host-display:inline-flex;--host-font-size:1rem;flex-wrap:wrap;justify-content:center;align-items:center;position:relative}label{padding:var(--host-padding-unit)}svg{min-width:14rem;max-width:18rem;margin:var(--host-padding-unit)0;fill:#0000;stroke:var(--host-color);stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;flex:16rem;overflow:visible}@media print{svg{flex:0 0 14rem;width:14rem}}path,line{vector-effect:non-scaling-stroke}path{opacity:.333}text,line{opacity:.667}g{outline:none}g[tabindex] path{opacity:1;fill:hsla(var(--scorad-h,0),var(--extent,\"0%\"),50%,var(--opacity,0))}:host(:not([aria-readonly=true])) g[tabindex] path{cursor:grab}:host(:not([aria-readonly=true])) :has(:active) g[tabindex] path{cursor:ns-resize}:is(:host(:not([aria-readonly=true])) :not(:has(:active)) g[tabindex]:hover path,:host(:not([aria-readonly=true])) :not(:has(:active)) g[tabindex]:hover line){stroke-width:2px;stroke:var(--host-primary-color);stroke-dasharray:2 3.5;opacity:1}:host(:not([aria-readonly=true])) g[tabindex]:focus path,:host(:not([aria-readonly=true])) g[tabindex]:active path,:host(:not([aria-readonly=true])) g[tabindex].selected path,:host(:not([aria-readonly=true])) g[tabindex]:focus line,:host(:not([aria-readonly=true])) g[tabindex]:active line,:host(:not([aria-readonly=true])) g[tabindex].selected line{stroke-width:2px;stroke:var(--host-primary-color);opacity:1;stroke-dasharray:none!important}g[tabindex] text{fill:var(--host-color);stroke:none;font-size:var(--host-font-size);text-anchor:end}g[tabindex].posterior-trunk text{text-anchor:start}:is(:host(:not([aria-readonly=true])) :not(:has(:active)) g[tabindex]:hover text,:host(:not([aria-readonly=true])) g[tabindex]:focus text,:host(:not([aria-readonly=true])) g[tabindex]:active text,:host(:not([aria-readonly=true])) g[tabindex].selected text){fill:var(--host-primary-color);opacity:1}";
971
971
  const selectScoradWeights = (e) => e?.child ? SCORAD_CHILD_WEIGHTS : SCORAD_ADULT_WEIGHTS, validateScoradData = (e) => {
972
972
  let t = [], n = (e, n, r) => {
973
973
  for (let [i, a] of Object.entries(e)) validateScoradData.isValid(a, r) || t.push(validateScoradData.formatError(n, i, r, a));
@@ -1046,11 +1046,25 @@ const ScoradResources = {
1046
1046
  };
1047
1047
  var tag$6 = "scorad-extent", HTMLScoradExtentElement = class extends HTMLComponentElement {
1048
1048
  connectedCallback() {
1049
- this.render();
1049
+ this.render(), this.addEventListener("focusin", this.handleFocusIn, { once: !0 });
1050
+ }
1051
+ disconnectedCallback() {
1052
+ this.removeEventListener("focusin", this.handleFocusIn);
1050
1053
  }
1054
+ handleFocusIn = () => {
1055
+ this.readonly || (this.committedValue = this.value, this.addEventListener("focusout", this.handleFocusOut, { once: !0 }));
1056
+ };
1057
+ handleFocusOut = () => {
1058
+ this.addEventListener("focusin", this.handleFocusIn, { once: !0 }), !areEqual(this.value, this.committedValue) && this.valueChange(this.value);
1059
+ };
1051
1060
  child = !1;
1061
+ readonly = !1;
1062
+ committedValue = SCORAD_EXTENT_DEFAULT;
1052
1063
  value = SCORAD_EXTENT_DEFAULT;
1053
1064
  score;
1065
+ setValue = (e) => {
1066
+ this.value = e, this.errors = void 0, this.valueInput(this.value);
1067
+ };
1054
1068
  errors;
1055
1069
  afterRender() {
1056
1070
  if (!this.value) {
@@ -1076,11 +1090,12 @@ var tag$6 = "scorad-extent", HTMLScoradExtentElement = class extends HTMLCompone
1076
1090
  render() {
1077
1091
  let e = (e) => {
1078
1092
  let t = (t) => {
1093
+ if (this.readonly) return;
1079
1094
  let n = Math.round(Math.min(100, Math.max(0, t)));
1080
- this.value = {
1095
+ this.setValue({
1081
1096
  ...this.value,
1082
1097
  [e]: n
1083
- }, this.errors = void 0, this.valueInput(this.value);
1098
+ });
1084
1099
  };
1085
1100
  return (n) => {
1086
1101
  if (!n) return;
@@ -1203,28 +1218,31 @@ var tag$6 = "scorad-extent", HTMLScoradExtentElement = class extends HTMLCompone
1203
1218
  valueChange;
1204
1219
  };
1205
1220
  __decorate([Att(AttBoolean)], HTMLScoradExtentElement.prototype, "child", void 0), __decorate([Att({
1221
+ name: "aria-readonly",
1222
+ ...AttTrueFalse
1223
+ })], HTMLScoradExtentElement.prototype, "readonly", void 0), __decorate([Att({
1206
1224
  name: "has-errors",
1207
1225
  write: (e) => e == null ? void 0 : "",
1208
1226
  read: !1
1209
- })], HTMLScoradExtentElement.prototype, "errors", void 0), __decorate([Watch("value", "errors")], HTMLScoradExtentElement.prototype, "afterRender", null), __decorate([Watch("child")], HTMLScoradExtentElement.prototype, "render", null), __decorate([Event()], HTMLScoradExtentElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradExtentElement.prototype, "valueChange", void 0), HTMLScoradExtentElement = __decorate([Component({
1227
+ })], HTMLScoradExtentElement.prototype, "errors", void 0), __decorate([Watch("value", "errors", "readonly")], HTMLScoradExtentElement.prototype, "afterRender", null), __decorate([Watch("child")], HTMLScoradExtentElement.prototype, "render", null), __decorate([Event()], HTMLScoradExtentElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradExtentElement.prototype, "valueChange", void 0), HTMLScoradExtentElement = __decorate([Component({
1210
1228
  tag: tag$6,
1211
1229
  css: [host_default, component_default$6],
1212
1230
  delegatesFocus: !0
1213
1231
  })], HTMLScoradExtentElement);
1214
- var component_default$4 = ":host{--host-display:inline-flex;flex-direction:column;position:relative}scorad-options{align-self:center;margin-left:auto}", component_default$5 = ":host{--host-display:inline-flex;--host-option-width:var(--scorad-option-width,1.5em);--host-option-height:var(--scorad-option-height,1.5em);--host-option-border:var(--scorad-option-border,solid 1px var(--host-color));--host-option-border-selected:var(--scorad-option-border-selected,solid 2px var(--host-color));--host-option-color:var(--scorad-option-color,var(--host-color));--host-option-background:var(--scorad-option-background,transparent);--host-option-color-selected:var(--scorad-option-color-selected,var(--host-background-color));--host-option-background-selected:var(--scorad-option-background-selected,var(--host-color));gap:var(--host-spacing-unit);margin:var(--host-padding-unit)0;outline:none;width:max-content;position:relative}span{height:var(--host-option-height);width:var(--host-option-width);outline:var(--host-option-border);border-radius:var(--host-border-radius);padding:calc(.5*var(--host-padding-unit))var(--host-padding-unit);color:var(--host-option-color);background:var(--host-option-background);cursor:pointer;touch-action:none}span:before{content:\"​\";touch-action:none;pointer-events:none}span[aria-selected=true]{outline:var(--host-option-border-selected);color:var(--host-option-color-selected);background:var(--host-option-background-selected)}", tag$5 = "scorad-options", HTMLScoradOptionsElement = class extends HTMLComponentElement {
1232
+ var component_default$4 = ":host{--host-display:inline-flex;flex-direction:column;position:relative}scorad-options{align-self:center;margin-left:auto}", component_default$5 = ":host{--host-display:inline-flex;--host-option-width:var(--scorad-option-width,1.5em);--host-option-height:var(--scorad-option-height,1.5em);--host-option-border:var(--scorad-option-border,solid 1px var(--host-color));--host-option-border-selected:var(--scorad-option-border-selected,solid 2px var(--host-color));--host-option-color:var(--scorad-option-color,var(--host-color));--host-option-background:var(--scorad-option-background,transparent);--host-option-color-selected:var(--scorad-option-color-selected,var(--host-background-color));--host-option-background-selected:var(--scorad-option-background-selected,var(--host-color));gap:var(--host-spacing-unit);margin:var(--host-padding-unit)0;outline:none;width:max-content;position:relative}span{height:var(--host-option-height);width:var(--host-option-width);outline:var(--host-option-border);border-radius:var(--host-border-radius);padding:calc(.5*var(--host-padding-unit))var(--host-padding-unit);color:var(--host-option-color);background:var(--host-option-background)}:host(:not([aria-readonly=true])) span{cursor:pointer}span:before{content:\"​\";touch-action:none;pointer-events:none}span[aria-selected=true]{outline:var(--host-option-border-selected);color:var(--host-option-color-selected);background:var(--host-option-background-selected)}", tag$5 = "scorad-options", HTMLScoradOptionsElement = class extends HTMLComponentElement {
1215
1233
  connectedCallback() {
1216
- this.render(), this.addEventListener("focusin", this._handleFocusIn, { once: !0 }), this.addEventListener("click", this._handleClick);
1234
+ this.render(), this.addEventListener("focusin", this.handleFocusIn, { once: !0 }), this.addEventListener("click", this.handleClick);
1217
1235
  }
1218
1236
  disconnectedCallback() {
1219
- this.addEventListener("click", this._handleClick);
1237
+ this.removeEventListener("focusin", this.handleFocusIn), this.removeEventListener("click", this.handleClick);
1220
1238
  }
1221
- _handleFocusIn = () => {
1222
- this.readonly || (this.committedValue = this.value, this.addEventListener("focusout", this._handleFocusOut, { once: !0 }), this.addEventListener("keydown", this._handleKeydown));
1239
+ handleFocusIn = () => {
1240
+ this.readonly || (this.committedValue = this.value, this.addEventListener("focusout", this.handleFocusOut, { once: !0 }), this.addEventListener("keydown", this.handleKeydown));
1223
1241
  };
1224
- _handleFocusOut = () => {
1225
- this.removeEventListener("keydown", this._handleKeydown), this.addEventListener("focusin", this._handleFocusIn, { once: !0 }), this.setValue(this.value), this.value !== this.committedValue && this.valueChange(this.value);
1242
+ handleFocusOut = () => {
1243
+ this.removeEventListener("keydown", this.handleKeydown), this.addEventListener("focusin", this.handleFocusIn, { once: !0 }), this.setValue(this.value), this.value !== this.committedValue && this.valueChange(this.value);
1226
1244
  };
1227
- _handleClick = (e) => {
1245
+ handleClick = (e) => {
1228
1246
  if (this.readonly) return;
1229
1247
  e.stopPropagation(), e.preventDefault(), this.focus();
1230
1248
  let t = e.composedPath()[0];
@@ -1232,7 +1250,7 @@ var component_default$4 = ":host{--host-display:inline-flex;flex-direction:colum
1232
1250
  let n = Number.parseInt(t.dataset.value);
1233
1251
  this.setValue(n);
1234
1252
  };
1235
- _handleKeydown = (e) => {
1253
+ handleKeydown = (e) => {
1236
1254
  let t = this.value ?? 0;
1237
1255
  switch (e.key) {
1238
1256
  case "ArrowLeft":
@@ -1268,7 +1286,7 @@ var component_default$4 = ":host{--host-display:inline-flex;flex-direction:colum
1268
1286
  afterRender() {
1269
1287
  this.shadowRoot && this.shadowRoot.querySelectorAll("span").forEach((e) => {
1270
1288
  let t = Number.parseInt(e.dataset.value);
1271
- if (e.ariaSelected = t === this.value ? "true" : "false", !this.showHue) return;
1289
+ if (e.setAttribute("aria-selected", t === this.value ? "true" : "false"), !this.showHue) return;
1272
1290
  if (t > this.value) {
1273
1291
  e.style.backgroundColor = "";
1274
1292
  return;
@@ -1289,7 +1307,7 @@ __decorate([Att({
1289
1307
  })], HTMLScoradOptionsElement.prototype, "max", void 0), __decorate([Att(AttBoolean)], HTMLScoradOptionsElement.prototype, "showHue", void 0), __decorate([Att({
1290
1308
  name: "aria-readonly",
1291
1309
  ...AttTrueFalse
1292
- })], HTMLScoradOptionsElement.prototype, "readonly", void 0), __decorate([Att({ write: !1 })], HTMLScoradOptionsElement.prototype, "text", void 0), __decorate([Watch("min", "max", "text")], HTMLScoradOptionsElement.prototype, "render", null), __decorate([Watch("value")], HTMLScoradOptionsElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradOptionsElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradOptionsElement.prototype, "valueChange", void 0), HTMLScoradOptionsElement = __decorate([Component({
1310
+ })], HTMLScoradOptionsElement.prototype, "readonly", void 0), __decorate([Att({ write: !1 })], HTMLScoradOptionsElement.prototype, "text", void 0), __decorate([Watch("min", "max", "text")], HTMLScoradOptionsElement.prototype, "render", null), __decorate([Watch("value", "readonly")], HTMLScoradOptionsElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradOptionsElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradOptionsElement.prototype, "valueChange", void 0), HTMLScoradOptionsElement = __decorate([Component({
1293
1311
  tag: tag$5,
1294
1312
  css: [host_default, component_default$5]
1295
1313
  })], HTMLScoradOptionsElement);
@@ -1297,6 +1315,7 @@ var tag$4 = "scorad-intensity", HTMLScoradIntensityElement = class extends HTMLC
1297
1315
  connectedCallback() {
1298
1316
  this.render();
1299
1317
  }
1318
+ readonly = !1;
1300
1319
  value = SCORAD_INTENSITY_DEFAULT;
1301
1320
  score;
1302
1321
  errors;
@@ -1311,7 +1330,7 @@ var tag$4 = "scorad-intensity", HTMLScoradIntensityElement = class extends HTMLC
1311
1330
  n.dataset.value = `${t}`;
1312
1331
  let r = n.querySelector("scorad-options");
1313
1332
  if (!r) return;
1314
- r.value = t, r.style.setProperty("--host-option-border", this.errors?.[e] == null ? null : "solid 1px var(--host-error-color)");
1333
+ r.value = t, r.readonly = this.readonly, r.style.setProperty("--host-option-border", this.errors?.[e] == null ? null : "solid 1px var(--host-error-color)");
1315
1334
  }
1316
1335
  }
1317
1336
  render() {
@@ -1321,12 +1340,17 @@ var tag$4 = "scorad-intensity", HTMLScoradIntensityElement = class extends HTMLC
1321
1340
  }
1322
1341
  renderLevel(e) {
1323
1342
  let t = (t) => {
1324
- t && t.addEventListener("value-input", (t) => {
1343
+ t && (t.addEventListener("value-input", (t) => {
1325
1344
  this.value = {
1326
1345
  ...this.value,
1327
1346
  [e]: t.detail
1328
- }, this.valueInput(this.value);
1329
- });
1347
+ }, this.errors = void 0, this.valueInput(this.value);
1348
+ }), t.addEventListener("value-change", (t) => {
1349
+ this.value = {
1350
+ ...this.value,
1351
+ [e]: t.detail
1352
+ }, this.errors = void 0, this.valueChange(this.value);
1353
+ }));
1330
1354
  }, n = ScoradResources.intensity[e];
1331
1355
  return html`
1332
1356
  <scorad-label class="${toKebabCase(e)} row"
@@ -1342,10 +1366,13 @@ var tag$4 = "scorad-intensity", HTMLScoradIntensityElement = class extends HTMLC
1342
1366
  valueChange;
1343
1367
  };
1344
1368
  __decorate([Att({
1369
+ name: "aria-readonly",
1370
+ ...AttTrueFalse
1371
+ })], HTMLScoradIntensityElement.prototype, "readonly", void 0), __decorate([Att({
1345
1372
  name: "has-errors",
1346
1373
  write: (e) => e == null ? void 0 : "",
1347
1374
  read: !1
1348
- })], HTMLScoradIntensityElement.prototype, "errors", void 0), __decorate([Watch("value", "errors")], HTMLScoradIntensityElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradIntensityElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradIntensityElement.prototype, "valueChange", void 0), HTMLScoradIntensityElement = __decorate([Component({
1375
+ })], HTMLScoradIntensityElement.prototype, "errors", void 0), __decorate([Watch("value", "errors", "readonly")], HTMLScoradIntensityElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradIntensityElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradIntensityElement.prototype, "valueChange", void 0), HTMLScoradIntensityElement = __decorate([Component({
1349
1376
  tag: tag$4,
1350
1377
  css: [host_default, component_default$4],
1351
1378
  delegatesFocus: !0
@@ -1354,6 +1381,7 @@ var component_default$3 = ":host{--host-display:inline-flex;--scorad-label-text-
1354
1381
  connectedCallback() {
1355
1382
  this.render();
1356
1383
  }
1384
+ readonly = !1;
1357
1385
  value = SCORAD_SUBJECTIVE_DEFAULT;
1358
1386
  score;
1359
1387
  errors;
@@ -1368,7 +1396,7 @@ var component_default$3 = ":host{--host-display:inline-flex;--scorad-label-text-
1368
1396
  n.dataset.value = `${t}`;
1369
1397
  let r = n.querySelector("scorad-options");
1370
1398
  if (!r) return;
1371
- r.value = t, r.style.setProperty("--host-option-border", this.errors?.[e] == null ? null : "solid 1px var(--host-error-color)");
1399
+ r.value = t, r.readonly = this.readonly, r.style.setProperty("--host-option-border", this.errors?.[e] == null ? null : "solid 1px var(--host-error-color)");
1372
1400
  }
1373
1401
  }
1374
1402
  render() {
@@ -1398,10 +1426,13 @@ var component_default$3 = ":host{--host-display:inline-flex;--scorad-label-text-
1398
1426
  valueChange;
1399
1427
  };
1400
1428
  __decorate([Att({
1429
+ name: "aria-readonly",
1430
+ ...AttTrueFalse
1431
+ })], HTMLScoradSubjectiveElement.prototype, "readonly", void 0), __decorate([Att({
1401
1432
  name: "has-errors",
1402
1433
  write: (e) => e == null ? void 0 : "",
1403
1434
  read: !1
1404
- })], HTMLScoradSubjectiveElement.prototype, "errors", void 0), __decorate([Watch("value", "errors")], HTMLScoradSubjectiveElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradSubjectiveElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradSubjectiveElement.prototype, "valueChange", void 0), HTMLScoradSubjectiveElement = __decorate([Component({
1435
+ })], HTMLScoradSubjectiveElement.prototype, "errors", void 0), __decorate([Watch("value", "errors", "readonly")], HTMLScoradSubjectiveElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradSubjectiveElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradSubjectiveElement.prototype, "valueChange", void 0), HTMLScoradSubjectiveElement = __decorate([Component({
1405
1436
  tag: tag$3,
1406
1437
  css: [host_default, component_default$3],
1407
1438
  delegatesFocus: !0
@@ -1410,6 +1441,7 @@ var component_default$2 = ":host{--host-display:inline-flex;padding:0 var(--host
1410
1441
  connectedCallback() {
1411
1442
  this.render();
1412
1443
  }
1444
+ readonly = !1;
1413
1445
  value = !1;
1414
1446
  afterRender() {
1415
1447
  if (this.value == null) {
@@ -1418,7 +1450,7 @@ var component_default$2 = ":host{--host-display:inline-flex;padding:0 var(--host
1418
1450
  }
1419
1451
  if (!this.shadowRoot) return;
1420
1452
  let e = this.shadowRoot.querySelector("scorad-options");
1421
- e && (e.value = this.value ? 1 : 0, e.text = ["Adult", "Child"]);
1453
+ e && (e.value = this.value ? 1 : 0, e.text = ["Adult", "Child"], e.readonly = this.readonly);
1422
1454
  }
1423
1455
  render() {
1424
1456
  return html`
@@ -1434,7 +1466,10 @@ var component_default$2 = ":host{--host-display:inline-flex;padding:0 var(--host
1434
1466
  valueInput;
1435
1467
  valueChange;
1436
1468
  };
1437
- __decorate([Watch("value")], HTMLScoradWeightingsElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradWeightingsElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradWeightingsElement.prototype, "valueChange", void 0), HTMLScoradWeightingsElement = __decorate([Component({
1469
+ __decorate([Att({
1470
+ name: "aria-readonly",
1471
+ ...AttTrueFalse
1472
+ })], HTMLScoradWeightingsElement.prototype, "readonly", void 0), __decorate([Watch("value", "readonly")], HTMLScoradWeightingsElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradWeightingsElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradWeightingsElement.prototype, "valueChange", void 0), HTMLScoradWeightingsElement = __decorate([Component({
1438
1473
  tag: tag$2,
1439
1474
  css: [host_default, component_default$2],
1440
1475
  delegatesFocus: !0
@@ -1453,12 +1488,12 @@ const getScoradScore = (e) => {
1453
1488
  };
1454
1489
  var component_default$1 = ":host{--host-display:var(--scorad-label-display,inline-flex);--host-options:var(--scorad-label-options,solid 1px var(--host-color));--host-text-min-width:var(--scorad-label-text-min-width,none);--host-text-max-width:var(--scorad-label-text-max-width,none);grid-column:1/3;grid-template-columns:subgrid;padding:var(--host-padding-unit);border-radius:var(--host-border-radius-outer);flex-wrap:wrap;align-content:baseline;align-items:baseline;position:relative}:host(:hover){outline:var(--host-outline-hover)}:host(:focus-within){outline:var(--host-outline-focus)}:host>label{min-width:var(--host-text-min-width);max-width:var(--host-text-max-width)}:host>label>slot[name=text]{transition:all .2s ease-in-out;display:block}:host>label>slot[name=description]{opacity:0;margin-bottom:-1.1em;transition:all .4s ease-in-out;display:block}:host(:hover)>label>slot[name=text],:host(:focus-within)>label>slot[name=text]{transform:translateY(-.5em)}:host(:hover)>label>slot[name=description],:host(:focus-within)>label>slot[name=description]{opacity:.667;transform:translateY(-.6em)}@media print{:host>label>slot[name=text]{transform:translateY(-.5em)}:host>label>slot[name=description]{opacity:.667;transform:translateY(-.6em)}}:host>label{padding:var(--host-padding-unit);flex:1;padding-left:0}", tag$1 = "scorad-label", HTMLScoradLabelElement = class extends HTMLComponentElement {
1455
1490
  connectedCallback() {
1456
- this.render(), this.addEventListener("click", this._handleClick);
1491
+ this.render(), this.addEventListener("click", this.handleClick);
1457
1492
  }
1458
1493
  diconnectedCallback() {
1459
- this.removeEventListener("click", this._handleClick);
1494
+ this.removeEventListener("click", this.handleClick);
1460
1495
  }
1461
- _handleClick = (e) => {
1496
+ handleClick = (e) => {
1462
1497
  this.querySelector(FOCUSABLE)?.focus();
1463
1498
  };
1464
1499
  text = "(label)";
@@ -1481,6 +1516,7 @@ var tag = "scorad-component", HTMLScoradElement = class extends HTMLComponentEle
1481
1516
  connectedCallback() {
1482
1517
  this.render();
1483
1518
  }
1519
+ readonly = !1;
1484
1520
  value = SCORAD_DEFAULT;
1485
1521
  showErrors = !1;
1486
1522
  afterRender() {
@@ -1497,13 +1533,13 @@ var tag = "scorad-component", HTMLScoradElement = class extends HTMLComponentEle
1497
1533
  }, n);
1498
1534
  e.innerText = `${t?.total ?? "Get Score"}`, e.style.color = this.showErrors && r != n ? "var(--host-error-color)" : "";
1499
1535
  let i = this.shadowRoot.querySelector("scorad-subjective");
1500
- i && (i.value = this.value?.subjective ?? SCORAD_SUBJECTIVE_DEFAULT, i.score = t?.B, this.showErrors && (i.errors = getValue(r, "subjective")));
1536
+ i && (i.value = this.value?.subjective ?? SCORAD_SUBJECTIVE_DEFAULT, i.score = t?.B, i.readonly = this.readonly, this.showErrors && (i.errors = getValue(r, "subjective")));
1501
1537
  let a = this.shadowRoot.querySelector("scorad-intensity");
1502
- a && (a.value = this.value?.intensity ?? SCORAD_INTENSITY_DEFAULT, a.score = t?.B, this.showErrors && (a.errors = getValue(r, "intensity")));
1538
+ a && (a.value = this.value?.intensity ?? SCORAD_INTENSITY_DEFAULT, a.score = t?.B, a.readonly = this.readonly, this.showErrors && (a.errors = getValue(r, "intensity")));
1503
1539
  let o = this.shadowRoot.querySelector("scorad-extent");
1504
- o && (o.child = this.value?.child ?? !1, o.value = this.value?.extent ?? SCORAD_EXTENT_DEFAULT, o.score = t?.A, this.showErrors && (o.errors = getValue(r, "extent")));
1540
+ o && (o.child = this.value?.child ?? !1, o.value = this.value?.extent ?? SCORAD_EXTENT_DEFAULT, o.score = t?.A, o.readonly = this.readonly, this.showErrors && (o.errors = getValue(r, "extent")));
1505
1541
  let s = this.shadowRoot.querySelector("scorad-weightings");
1506
- s && (s.value = this.value?.child ?? !1);
1542
+ s && (s.value = this.value?.child ?? !1, s.readonly = this.readonly);
1507
1543
  }
1508
1544
  render() {
1509
1545
  return html`
@@ -1511,20 +1547,30 @@ var tag = "scorad-component", HTMLScoradElement = class extends HTMLComponentEle
1511
1547
  <h3 slot="text">A</h3>
1512
1548
  <p slot="description">extent - effected surface area</p>
1513
1549
  <scorad-weightings ${(e) => {
1514
- e instanceof HTMLScoradWeightingsElement && e.addEventListener("value-input", (e) => {
1550
+ e instanceof HTMLScoradWeightingsElement && (e.addEventListener("value-input", (e) => {
1515
1551
  this.value = {
1516
1552
  ...this.value,
1517
1553
  child: e.detail
1518
- };
1519
- });
1554
+ }, this.valueInput(this.value);
1555
+ }), e.addEventListener("value-change", (e) => {
1556
+ this.value = {
1557
+ ...this.value,
1558
+ child: e.detail
1559
+ }, this.valueChange(this.value);
1560
+ }));
1520
1561
  }}></scorad-weightings>
1521
1562
  <scorad-extent tabindex="0" ${(e) => {
1522
- e instanceof HTMLScoradExtentElement && e.addEventListener("value-input", (e) => {
1563
+ e instanceof HTMLScoradExtentElement && (e.addEventListener("value-input", (e) => {
1523
1564
  this.value = {
1524
1565
  ...this.value,
1525
1566
  extent: e.detail
1526
- };
1527
- });
1567
+ }, this.valueInput(this.value);
1568
+ }), e.addEventListener("value-change", (e) => {
1569
+ this.value = {
1570
+ ...this.value,
1571
+ extent: e.detail
1572
+ }, this.valueChange(this.value);
1573
+ }));
1528
1574
  }}></scorad-extent>
1529
1575
  </scorad-label>
1530
1576
 
@@ -1532,12 +1578,17 @@ var tag = "scorad-component", HTMLScoradElement = class extends HTMLComponentEle
1532
1578
  <h3 slot="text">B</h3>
1533
1579
  <p slot="description">intensity - clinical sign severity</p>
1534
1580
  <scorad-intensity tabindex="0" ${(e) => {
1535
- e instanceof HTMLScoradIntensityElement && e.addEventListener("value-input", (e) => {
1581
+ e instanceof HTMLScoradIntensityElement && (e.addEventListener("value-input", (e) => {
1536
1582
  this.value = {
1537
1583
  ...this.value,
1538
1584
  intensity: e.detail
1539
- };
1540
- });
1585
+ }, this.valueInput(this.value);
1586
+ }), e.addEventListener("value-change", (e) => {
1587
+ this.value = {
1588
+ ...this.value,
1589
+ intensity: e.detail
1590
+ }, this.valueChange(this.value);
1591
+ }));
1541
1592
  }}></scorad-intensity>
1542
1593
  </scorad-label>
1543
1594
 
@@ -1545,12 +1596,17 @@ var tag = "scorad-component", HTMLScoradElement = class extends HTMLComponentEle
1545
1596
  <h3 slot="text">C</h3>
1546
1597
  <p slot="description">subjective - patient reported symptoms</p>
1547
1598
  <scorad-subjective tabindex="0" ${(e) => {
1548
- e instanceof HTMLScoradSubjectiveElement && e.addEventListener("value-input", (e) => {
1599
+ e instanceof HTMLScoradSubjectiveElement && (e.addEventListener("value-input", (e) => {
1549
1600
  this.value = {
1550
1601
  ...this.value,
1551
1602
  subjective: e.detail
1552
- };
1553
- });
1603
+ }, this.valueInput(this.value);
1604
+ }), e.addEventListener("value-change", (e) => {
1605
+ this.value = {
1606
+ ...this.value,
1607
+ subjective: e.detail
1608
+ }, this.valueChange(this.value);
1609
+ }));
1554
1610
  }}></scorad-subjective>
1555
1611
  </scorad-label>
1556
1612
 
@@ -1560,7 +1616,7 @@ var tag = "scorad-component", HTMLScoradElement = class extends HTMLComponentEle
1560
1616
  ${(e) => {
1561
1617
  e instanceof HTMLScoradLabelElement && e.addEventListener("click", () => {
1562
1618
  if (!this.shadowRoot) return;
1563
- this.showErrors = !0, this.afterRender();
1619
+ this.showErrors = !0;
1564
1620
  let e = this.shadowRoot.querySelector("[has-errors]");
1565
1621
  e && (e.focus({ preventScroll: !0 }), e.scrollIntoView({
1566
1622
  behavior: "smooth",
@@ -1573,8 +1629,13 @@ var tag = "scorad-component", HTMLScoradElement = class extends HTMLComponentEle
1573
1629
  </scorad-label>
1574
1630
  `;
1575
1631
  }
1632
+ valueInput;
1633
+ valueChange;
1576
1634
  };
1577
- __decorate([Watch("value")], HTMLScoradElement.prototype, "afterRender", null), HTMLScoradElement = __decorate([Component({
1635
+ __decorate([Att({
1636
+ name: "aria-readonly",
1637
+ ...AttTrueFalse
1638
+ })], HTMLScoradElement.prototype, "readonly", void 0), __decorate([Watch("value", "showErrors", "readonly")], HTMLScoradElement.prototype, "afterRender", null), __decorate([Event()], HTMLScoradElement.prototype, "valueInput", void 0), __decorate([Event()], HTMLScoradElement.prototype, "valueChange", void 0), HTMLScoradElement = __decorate([Component({
1578
1639
  tag,
1579
1640
  css: [host_default, component_default],
1580
1641
  delegatesFocus: !0