@qti-components/base 2.0.1 → 2.1.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.
package/dist/index.d.ts CHANGED
@@ -23,5 +23,6 @@ export * from './lib/expression-result';
23
23
  export * from './lib/interaction.interface';
24
24
  export * from './lib/variables';
25
25
  export * from './lib/qti-rule-base';
26
+ export * from './utils/custom-state-set';
26
27
  export * from './utils/scoring-helper';
27
28
  export * from './utils/utils';
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ export * from './lib/expression-result';
23
23
  export * from './lib/interaction.interface';
24
24
  export * from './lib/variables';
25
25
  export * from './lib/qti-rule-base';
26
+ export * from './utils/custom-state-set';
26
27
  export * from './utils/scoring-helper';
27
28
  export * from './utils/utils';
28
29
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AAEpB,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AAEvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oCAAoC,CAAC;AAEnD,cAAc,wBAAwB,CAAC;AACvC,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAE1C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC","sourcesContent":["import './register';\n\nexport * from './context/computed-item.context';\nexport * from './context/types/computed-item.types';\nexport * from './context/computed.context';\nexport * from './context/types/computed.types';\nexport * from './context/config.context';\nexport * from './context/interaction.context';\nexport * from './context/item.context';\nexport * from './context/types/item.types';\nexport * from './context/qti.context';\nexport * from './context/session.context';\nexport * from './context/test.context';\n\nexport * from './styles/box-sizing.styles';\nexport * from './styles/drop-region.styles';\nexport * from './styles/validation-message.styles';\n\nexport * from './abstract/interaction';\nexport * from './abstract/qti-variable-declaration';\nexport * from './abstract/qti-condition-expression';\nexport * from './abstract/qti-feedback';\nexport * from './abstract/qti-expression';\n\nexport * from './lib/response';\nexport * from './lib/expression-result';\nexport * from './lib/interaction.interface';\nexport * from './lib/variables';\nexport * from './lib/qti-rule-base';\n\nexport * from './utils/scoring-helper';\nexport * from './utils/utils';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AAEpB,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AAEvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oCAAoC,CAAC;AAEnD,cAAc,wBAAwB,CAAC;AACvC,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAE1C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC","sourcesContent":["import './register';\n\nexport * from './context/computed-item.context';\nexport * from './context/types/computed-item.types';\nexport * from './context/computed.context';\nexport * from './context/types/computed.types';\nexport * from './context/config.context';\nexport * from './context/interaction.context';\nexport * from './context/item.context';\nexport * from './context/types/item.types';\nexport * from './context/qti.context';\nexport * from './context/session.context';\nexport * from './context/test.context';\n\nexport * from './styles/box-sizing.styles';\nexport * from './styles/drop-region.styles';\nexport * from './styles/validation-message.styles';\n\nexport * from './abstract/interaction';\nexport * from './abstract/qti-variable-declaration';\nexport * from './abstract/qti-condition-expression';\nexport * from './abstract/qti-feedback';\nexport * from './abstract/qti-expression';\n\nexport * from './lib/response';\nexport * from './lib/expression-result';\nexport * from './lib/interaction.interface';\nexport * from './lib/variables';\nexport * from './lib/qti-rule-base';\n\nexport * from './utils/custom-state-set';\nexport * from './utils/scoring-helper';\nexport * from './utils/utils';\n"]}
package/dist/register.js CHANGED
@@ -1,4 +1,8 @@
1
1
  import { qtiBaseElements } from './elements';
2
+ import { installCustomStateSetFallback } from './utils/custom-state-set';
3
+ // Before any element is defined, so the patched `attachInternals` is in place by
4
+ // the time the first interaction upgrades. No-op where `states` works natively.
5
+ installCustomStateSetFallback();
2
6
  for (const { tag, ctor } of qtiBaseElements) {
3
7
  if (!customElements.get(tag)) {
4
8
  customElements.define(tag, ctor);
@@ -1 +1 @@
1
- {"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,eAAe,EAAE,CAAC;IAC5C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;AACH,CAAC","sourcesContent":["import { qtiBaseElements } from './elements';\n\nfor (const { tag, ctor } of qtiBaseElements) {\n if (!customElements.get(tag)) {\n customElements.define(tag, ctor);\n }\n}\n"]}
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAEzE,iFAAiF;AACjF,gFAAgF;AAChF,6BAA6B,EAAE,CAAC;AAEhC,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,eAAe,EAAE,CAAC;IAC5C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;AACH,CAAC","sourcesContent":["import { qtiBaseElements } from './elements';\nimport { installCustomStateSetFallback } from './utils/custom-state-set';\n\n// Before any element is defined, so the patched `attachInternals` is in place by\n// the time the first interaction upgrades. No-op where `states` works natively.\ninstallCustomStateSetFallback();\n\nfor (const { tag, ctor } of qtiBaseElements) {\n if (!customElements.get(tag)) {\n customElements.define(tag, ctor);\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Custom-state support shim for browsers this library cannot use natively.
3
+ *
4
+ * Interactions track selection and correct/incorrect marking through
5
+ * `internals.states`, with bare state names (`checked`, `radio`,
6
+ * `correct-response`, …). Two browser profiles cannot service that:
7
+ *
8
+ * - Safari 16.4–17.3 implement `ElementInternals` but not `CustomStateSet`, so
9
+ * `internals.states` is `undefined` and reading it throws — "undefined is not
10
+ * an object (evaluating 'internals.states.has')".
11
+ * - Chrome and Edge before the CSS custom-state spec change expose `states` but
12
+ * reject names that do not start with `--`, so `states.add('radio')` throws
13
+ * "The specified value 'radio' must start with '--'".
14
+ *
15
+ * On both, picking a choice throws rather than registering. `attachInternals`
16
+ * itself is unguarded throughout the interaction base classes, which puts the
17
+ * hard support floor at Safari 16.4 — inside the range that breaks.
18
+ *
19
+ * The shim replaces `states` with a permissive `Set` and mirrors its contents to
20
+ * a space-separated `data-state` attribute on the host, because these browsers'
21
+ * CSS parsers also drop any selector list containing `:state()`. The theme's
22
+ * built stylesheets pair every `:state(x)` with a `[data-state~='x']` arm (see
23
+ * `tools/postcss/custom-state-fallback.mjs`), so styling survives too.
24
+ *
25
+ * Support is classified by behaviour rather than by browser version, and the
26
+ * shim is not installed at all where `states` works.
27
+ */
28
+ /** A permissive `CustomStateSet` that mirrors its contents to `data-state`. */
29
+ export declare class CustomStateSetShim extends Set<string> {
30
+ #private;
31
+ constructor(host: HTMLElement);
32
+ add(state: string): this;
33
+ delete(state: string): boolean;
34
+ clear(): void;
35
+ }
36
+ export type CustomStateSupport = 'missing' | 'legacy' | 'modern';
37
+ /**
38
+ * Classify native custom-state support by behaviour:
39
+ *
40
+ * - `missing`: `ElementInternals` carries no `states` at all.
41
+ * - `legacy`: `states` exists but rejects a bare name.
42
+ * - `modern`: `states` exists and accepts a bare name — no shim wanted.
43
+ *
44
+ * `attachInternals` only works on a custom element, so the probe registers a
45
+ * throwaway one. That happens solely when `states` is present, so the `missing`
46
+ * path never defines anything.
47
+ */
48
+ export declare function classifyCustomStateSupport(attachInternals: (this: HTMLElement) => ElementInternals): CustomStateSupport;
49
+ /**
50
+ * Patch `HTMLElement.prototype.attachInternals` so every element's `states` is
51
+ * the shim, on the two profiles that need it. Idempotent, and a no-op where
52
+ * native support is usable.
53
+ *
54
+ * Returns the classification that was acted on, which is what the tests assert.
55
+ */
56
+ export declare function installCustomStateSetFallback(): CustomStateSupport | 'already-installed';
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Custom-state support shim for browsers this library cannot use natively.
3
+ *
4
+ * Interactions track selection and correct/incorrect marking through
5
+ * `internals.states`, with bare state names (`checked`, `radio`,
6
+ * `correct-response`, …). Two browser profiles cannot service that:
7
+ *
8
+ * - Safari 16.4–17.3 implement `ElementInternals` but not `CustomStateSet`, so
9
+ * `internals.states` is `undefined` and reading it throws — "undefined is not
10
+ * an object (evaluating 'internals.states.has')".
11
+ * - Chrome and Edge before the CSS custom-state spec change expose `states` but
12
+ * reject names that do not start with `--`, so `states.add('radio')` throws
13
+ * "The specified value 'radio' must start with '--'".
14
+ *
15
+ * On both, picking a choice throws rather than registering. `attachInternals`
16
+ * itself is unguarded throughout the interaction base classes, which puts the
17
+ * hard support floor at Safari 16.4 — inside the range that breaks.
18
+ *
19
+ * The shim replaces `states` with a permissive `Set` and mirrors its contents to
20
+ * a space-separated `data-state` attribute on the host, because these browsers'
21
+ * CSS parsers also drop any selector list containing `:state()`. The theme's
22
+ * built stylesheets pair every `:state(x)` with a `[data-state~='x']` arm (see
23
+ * `tools/postcss/custom-state-fallback.mjs`), so styling survives too.
24
+ *
25
+ * Support is classified by behaviour rather than by browser version, and the
26
+ * shim is not installed at all where `states` works.
27
+ */
28
+ /** A permissive `CustomStateSet` that mirrors its contents to `data-state`. */
29
+ export class CustomStateSetShim extends Set {
30
+ #host;
31
+ constructor(host) {
32
+ super();
33
+ this.#host = host;
34
+ }
35
+ add(state) {
36
+ super.add(state);
37
+ this.#reflect();
38
+ return this;
39
+ }
40
+ delete(state) {
41
+ const deleted = super.delete(state);
42
+ this.#reflect();
43
+ return deleted;
44
+ }
45
+ clear() {
46
+ super.clear();
47
+ this.#reflect();
48
+ }
49
+ #reflect() {
50
+ // A custom element must not gain attributes while it is upgrading, so defer
51
+ // the write until the element is connected.
52
+ if (this.#host.isConnected)
53
+ this.#write();
54
+ else
55
+ queueMicrotask(() => this.#write());
56
+ }
57
+ #write() {
58
+ if (this.size === 0)
59
+ this.#host.removeAttribute('data-state');
60
+ else
61
+ this.#host.setAttribute('data-state', Array.from(this).join(' '));
62
+ }
63
+ }
64
+ const PROBE_TAG = 'custom-state-support-probe';
65
+ /**
66
+ * Classify native custom-state support by behaviour:
67
+ *
68
+ * - `missing`: `ElementInternals` carries no `states` at all.
69
+ * - `legacy`: `states` exists but rejects a bare name.
70
+ * - `modern`: `states` exists and accepts a bare name — no shim wanted.
71
+ *
72
+ * `attachInternals` only works on a custom element, so the probe registers a
73
+ * throwaway one. That happens solely when `states` is present, so the `missing`
74
+ * path never defines anything.
75
+ */
76
+ export function classifyCustomStateSupport(attachInternals) {
77
+ if (!('states' in ElementInternals.prototype))
78
+ return 'missing';
79
+ if (typeof customElements === 'undefined')
80
+ return 'modern';
81
+ try {
82
+ if (!customElements.get(PROBE_TAG))
83
+ customElements.define(PROBE_TAG, class extends HTMLElement {
84
+ });
85
+ const states = attachInternals.call(document.createElement(PROBE_TAG)).states;
86
+ if (!states)
87
+ return 'missing';
88
+ // A bare name throws SyntaxError under the legacy spec and never does under
89
+ // the current one, so classify on the throw alone — not on whether the value
90
+ // sticks, which some environments do not honour on a detached element.
91
+ states.add('probe');
92
+ states.delete('probe');
93
+ return 'modern';
94
+ }
95
+ catch {
96
+ return 'legacy';
97
+ }
98
+ }
99
+ let installed = false;
100
+ /**
101
+ * Patch `HTMLElement.prototype.attachInternals` so every element's `states` is
102
+ * the shim, on the two profiles that need it. Idempotent, and a no-op where
103
+ * native support is usable.
104
+ *
105
+ * Returns the classification that was acted on, which is what the tests assert.
106
+ */
107
+ export function installCustomStateSetFallback() {
108
+ if (installed)
109
+ return 'already-installed';
110
+ if (typeof HTMLElement === 'undefined' || typeof ElementInternals === 'undefined')
111
+ return 'modern';
112
+ const native = HTMLElement.prototype.attachInternals;
113
+ if (!native)
114
+ return 'modern';
115
+ const support = classifyCustomStateSupport(native);
116
+ if (support === 'modern')
117
+ return support;
118
+ installed = true;
119
+ HTMLElement.prototype.attachInternals = function attachInternals() {
120
+ const internals = native.call(this);
121
+ Object.defineProperty(internals, 'states', {
122
+ configurable: true,
123
+ enumerable: true,
124
+ value: new CustomStateSetShim(this)
125
+ });
126
+ return internals;
127
+ };
128
+ return support;
129
+ }
130
+ //# sourceMappingURL=custom-state-set.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-state-set.js","sourceRoot":"","sources":["../../src/utils/custom-state-set.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Custom-state support shim for browsers this library cannot use natively.\n *\n * Interactions track selection and correct/incorrect marking through\n * `internals.states`, with bare state names (`checked`, `radio`,\n * `correct-response`, …). Two browser profiles cannot service that:\n *\n * - Safari 16.4–17.3 implement `ElementInternals` but not `CustomStateSet`, so\n * `internals.states` is `undefined` and reading it throws — \"undefined is not\n * an object (evaluating 'internals.states.has')\".\n * - Chrome and Edge before the CSS custom-state spec change expose `states` but\n * reject names that do not start with `--`, so `states.add('radio')` throws\n * \"The specified value 'radio' must start with '--'\".\n *\n * On both, picking a choice throws rather than registering. `attachInternals`\n * itself is unguarded throughout the interaction base classes, which puts the\n * hard support floor at Safari 16.4 — inside the range that breaks.\n *\n * The shim replaces `states` with a permissive `Set` and mirrors its contents to\n * a space-separated `data-state` attribute on the host, because these browsers'\n * CSS parsers also drop any selector list containing `:state()`. The theme's\n * built stylesheets pair every `:state(x)` with a `[data-state~='x']` arm (see\n * `tools/postcss/custom-state-fallback.mjs`), so styling survives too.\n *\n * Support is classified by behaviour rather than by browser version, and the\n * shim is not installed at all where `states` works.\n */\n\n/** A permissive `CustomStateSet` that mirrors its contents to `data-state`. */\nexport class CustomStateSetShim extends Set<string> {\n readonly #host: HTMLElement;\n\n constructor(host: HTMLElement) {\n super();\n this.#host = host;\n }\n\n override add(state: string): this {\n super.add(state);\n this.#reflect();\n return this;\n }\n\n override delete(state: string): boolean {\n const deleted = super.delete(state);\n this.#reflect();\n return deleted;\n }\n\n override clear(): void {\n super.clear();\n this.#reflect();\n }\n\n #reflect(): void {\n // A custom element must not gain attributes while it is upgrading, so defer\n // the write until the element is connected.\n if (this.#host.isConnected) this.#write();\n else queueMicrotask(() => this.#write());\n }\n\n #write(): void {\n if (this.size === 0) this.#host.removeAttribute('data-state');\n else this.#host.setAttribute('data-state', Array.from(this).join(' '));\n }\n}\n\nexport type CustomStateSupport = 'missing' | 'legacy' | 'modern';\n\nconst PROBE_TAG = 'custom-state-support-probe';\n\n/**\n * Classify native custom-state support by behaviour:\n *\n * - `missing`: `ElementInternals` carries no `states` at all.\n * - `legacy`: `states` exists but rejects a bare name.\n * - `modern`: `states` exists and accepts a bare name — no shim wanted.\n *\n * `attachInternals` only works on a custom element, so the probe registers a\n * throwaway one. That happens solely when `states` is present, so the `missing`\n * path never defines anything.\n */\nexport function classifyCustomStateSupport(\n attachInternals: (this: HTMLElement) => ElementInternals\n): CustomStateSupport {\n if (!('states' in ElementInternals.prototype)) return 'missing';\n if (typeof customElements === 'undefined') return 'modern';\n\n try {\n if (!customElements.get(PROBE_TAG)) customElements.define(PROBE_TAG, class extends HTMLElement {});\n const states = attachInternals.call(document.createElement(PROBE_TAG)).states;\n if (!states) return 'missing';\n // A bare name throws SyntaxError under the legacy spec and never does under\n // the current one, so classify on the throw alone — not on whether the value\n // sticks, which some environments do not honour on a detached element.\n states.add('probe');\n states.delete('probe');\n return 'modern';\n } catch {\n return 'legacy';\n }\n}\n\nlet installed = false;\n\n/**\n * Patch `HTMLElement.prototype.attachInternals` so every element's `states` is\n * the shim, on the two profiles that need it. Idempotent, and a no-op where\n * native support is usable.\n *\n * Returns the classification that was acted on, which is what the tests assert.\n */\nexport function installCustomStateSetFallback(): CustomStateSupport | 'already-installed' {\n if (installed) return 'already-installed';\n if (typeof HTMLElement === 'undefined' || typeof ElementInternals === 'undefined') return 'modern';\n\n const native = HTMLElement.prototype.attachInternals;\n if (!native) return 'modern';\n\n const support = classifyCustomStateSupport(native);\n if (support === 'modern') return support;\n\n installed = true;\n HTMLElement.prototype.attachInternals = function attachInternals(this: HTMLElement): ElementInternals {\n const internals = native.call(this);\n Object.defineProperty(internals, 'states', {\n configurable: true,\n enumerable: true,\n value: new CustomStateSetShim(this)\n });\n return internals;\n };\n\n return support;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qti-components/base",
3
3
  "description": "QTI shared utilities",
4
- "version": "2.0.1",
4
+ "version": "2.1.0",
5
5
  "author": "",
6
6
  "dependencies": {
7
7
  "@qti-components/utilities": "^1.3.3"