@citolab/qti-components 8.0.0 → 8.0.2
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/cdn/index.global.js +1 -1
- package/cdn/index.js +690 -316
- package/dist/base.d.ts +135 -9
- package/dist/base.js +1 -1
- package/dist/chunk-4DB4SC5K.js +100 -0
- package/dist/chunk-4DB4SC5K.js.map +1 -0
- package/dist/{chunk-WVVMNF74.js → chunk-7AVWRQIS.js} +188 -160
- package/dist/{chunk-WVVMNF74.js.map → chunk-7AVWRQIS.js.map} +1 -1
- package/dist/{chunk-MGF5F3S2.js → chunk-BQFWCZFH.js} +2 -2
- package/dist/{chunk-5QYII4OZ.js → chunk-CK7AV2UJ.js} +147 -101
- package/dist/chunk-CK7AV2UJ.js.map +1 -0
- package/dist/chunk-G6GFQDVN.js +7 -0
- package/dist/chunk-G6GFQDVN.js.map +1 -0
- package/dist/{chunk-LZJHHZQ2.js → chunk-HBSWJC3B.js} +643 -553
- package/dist/chunk-HBSWJC3B.js.map +1 -0
- package/dist/chunk-HQESINXH.js +834 -0
- package/dist/chunk-HQESINXH.js.map +1 -0
- package/dist/{chunk-KYEZRYO6.js → chunk-J5SQLKNP.js} +145 -119
- package/dist/{chunk-KYEZRYO6.js.map → chunk-J5SQLKNP.js.map} +1 -1
- package/dist/{chunk-V5G7X53Y.js → chunk-NJQHUUTN.js} +2 -2
- package/dist/{chunk-W4QZPCNC.js → chunk-RSVRS5AK.js} +59 -58
- package/dist/chunk-RSVRS5AK.js.map +1 -0
- package/dist/{chunk-5SJS36BI.js → chunk-TWPXES3S.js} +642 -673
- package/dist/chunk-TWPXES3S.js.map +1 -0
- package/dist/{chunk-NA2N2WSG.js → chunk-VAE6HBKR.js} +176 -152
- package/dist/{chunk-NA2N2WSG.js.map → chunk-VAE6HBKR.js.map} +1 -1
- package/dist/{chunk-MJVPNN7W.js → chunk-ZII6M3SN.js} +2 -2
- package/dist/computed-item.types.d-BKZdSLLJ.d.ts +19 -0
- package/dist/computed.types.d-DpKIYO6e.d.ts +44 -0
- package/dist/config.context.d-BvU9r-Mg.d.ts +91 -0
- package/dist/corrections.d.ts +415 -1
- package/dist/corrections.js +8 -9
- package/dist/elements.d.ts +393 -1
- package/dist/elements.js +4 -5
- package/dist/index.d.ts +27 -9
- package/dist/index.js +21 -14
- package/dist/interaction.d-CJiMv8H7.d.ts +126 -0
- package/dist/interaction.interface.d-CvjHM2bi.d.ts +46 -0
- package/dist/interactions.d.ts +281 -1
- package/dist/interactions.js +4 -5
- package/dist/item.css +588 -214
- package/dist/item.d.ts +92 -1
- package/dist/item.js +5 -5
- package/dist/item.types.d-QU7LljEK.d.ts +82 -0
- package/dist/loader.d.ts +22 -1
- package/dist/processing.d.ts +730 -1
- package/dist/processing.js +2 -2
- package/dist/qti-components-jsx.d.ts +854 -844
- package/dist/qti-components-jsx.js +1 -0
- package/dist/qti-condition-expression.d-aMvBvpc6.d.ts +8 -0
- package/dist/qti-expression.d-BietT_8b.d.ts +31 -0
- package/dist/qti-feedback.d-DZbad0RN.d.ts +21 -0
- package/dist/qti-rule-base.d-ajOnfGXY.d.ts +16 -0
- package/dist/qti-test.d-_YAlhDbt.d.ts +702 -0
- package/dist/qti-text-entry-interaction.d-BGdfV9qG.d.ts +1567 -0
- package/dist/qti-transform-test.d-BL7GGFJj.d.ts +70 -0
- package/dist/qti.context.d-CejJMlfZ.d.ts +50 -0
- package/dist/response.d-_8y4kjGP.d.ts +106 -0
- package/dist/test.d.ts +32 -1
- package/dist/test.js +14 -7
- package/dist/transformers.d.ts +18 -1
- package/package.json +15 -11
- package/dist/chunk-5QYII4OZ.js.map +0 -1
- package/dist/chunk-5SJS36BI.js.map +0 -1
- package/dist/chunk-64XSIBGO.js +0 -241
- package/dist/chunk-64XSIBGO.js.map +0 -1
- package/dist/chunk-H3J7LFRO.js +0 -2243
- package/dist/chunk-H3J7LFRO.js.map +0 -1
- package/dist/chunk-LZJHHZQ2.js.map +0 -1
- package/dist/chunk-W4QZPCNC.js.map +0 -1
- package/dist/chunk-WCQ26LV7.js +0 -102
- package/dist/chunk-WCQ26LV7.js.map +0 -1
- package/dist/chunk-X3E5HXG2.js +0 -20
- package/dist/chunk-X3E5HXG2.js.map +0 -1
- /package/dist/{chunk-MGF5F3S2.js.map → chunk-BQFWCZFH.js.map} +0 -0
- /package/dist/{chunk-V5G7X53Y.js.map → chunk-NJQHUUTN.js.map} +0 -0
- /package/dist/{chunk-MJVPNN7W.js.map → chunk-ZII6M3SN.js.map} +0 -0
package/dist/base.d.ts
CHANGED
|
@@ -1,9 +1,135 @@
|
|
|
1
|
-
|
|
2
|
-
import '
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import '
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { C as ComputedItemContext } from './computed-item.types.d-BKZdSLLJ.js';
|
|
2
|
+
import { C as ComputedContext } from './computed.types.d-DpKIYO6e.js';
|
|
3
|
+
export { a as ComputedItem, I as INITIAL_SESSION_CONTEXT, S as SessionContext, V as View, s as sessionContext } from './computed.types.d-DpKIYO6e.js';
|
|
4
|
+
export { C as ConfigContext, V as ValidationDisplayMode, c as configContext } from './config.context.d-BvU9r-Mg.js';
|
|
5
|
+
export { C as ChoiceRole, I as Interaction, a as InteractionContext, i as interactionContext } from './interaction.d-CJiMv8H7.js';
|
|
6
|
+
import { I as ItemContext, B as BaseType } from './item.types.d-QU7LljEK.js';
|
|
7
|
+
export { C as Calculate, a as Cardinality, M as Multiple, O as Ordered, b as OutcomeVariable, Q as QtiAreaMapEntry, c as QtiAreaMapping, d as QtiMapEntry, e as QtiMapping, R as ResponseInteraction, f as ResponseVariable, T as TemplateVariable, V as VariableDeclaration, g as VariableValue, h as areaShape, i as directedPair, j as float, k as integer, l as itemContextVariables } from './item.types.d-QU7LljEK.js';
|
|
8
|
+
export { Q as QtiContext, a as QtiContextType, S as Seed, b as asSeed, i as isSeed, q as qtiContext } from './qti.context.d-CejJMlfZ.js';
|
|
9
|
+
export { I as INITIAL_TEST_CONTEXT, Q as QtiExpression, a as QtiExpressionBase, T as TestContext, t as testContext } from './qti-expression.d-BietT_8b.js';
|
|
10
|
+
import * as lit from 'lit';
|
|
11
|
+
export { Q as QtiFeedback, a as QtiVariableDeclaration } from './qti-feedback.d-DZbad0RN.js';
|
|
12
|
+
export { Q as QtiConditionExpression } from './qti-condition-expression.d-aMvBvpc6.js';
|
|
13
|
+
export { D as DirectedPair, I as Identifier, P as Point, R as ResponseValue, i as iterResponseValues, p as parsePair, a as parsePoint, b as parseResponseAttribute, r as responseAttributeConverter, s as serializePair, c as serializePoint, d as serializeResponseAttribute } from './response.d-_8y4kjGP.js';
|
|
14
|
+
export { F as FormAssociatedInteraction, I as IInteraction, R as ReadonlyInteraction, a as RegisteredInteraction, b as ResettableInteraction, c as ResponseIdentifiedInteraction, d as ResponseInteractionElement, V as ValidatableInteraction } from './interaction.interface.d-CvjHM2bi.js';
|
|
15
|
+
export { Q as QtiRuleBase, a as QtiRuleElement } from './qti-rule-base.d-ajOnfGXY.js';
|
|
16
|
+
|
|
17
|
+
declare const computedItemContext: {
|
|
18
|
+
__context__: Readonly<ComputedItemContext>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
declare const computedContext: {
|
|
22
|
+
__context__: Readonly<ComputedContext>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
declare const itemContext: {
|
|
26
|
+
__context__: ItemContext;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The border-box reset, for shadow trees.
|
|
31
|
+
*
|
|
32
|
+
* `modern-normalize` applies `*, ::before, ::after { box-sizing: border-box }` to the document,
|
|
33
|
+
* but a universal selector never crosses a shadow boundary: every shadow root starts over at
|
|
34
|
+
* the UA default, `content-box`. So each component has to opt in, and until now 15 of them did
|
|
35
|
+
* so ad hoc — one of them with `content-box`, one with `!important`.
|
|
36
|
+
*
|
|
37
|
+
* Why this matters beyond tidiness: under `content-box`, padding and border are *size*. A rule
|
|
38
|
+
* that only means to repaint a chip — `:state(dragging) { border: none }` — silently shrinks it
|
|
39
|
+
* by two pixels per axis, the drag bank rewraps, and a drop target slides out from under the
|
|
40
|
+
* user's pointer mid-drag. Border-box is what makes "a state may repaint but never re-lay-out"
|
|
41
|
+
* a rule the CSS can actually keep.
|
|
42
|
+
*
|
|
43
|
+
* `:host` is set explicitly and descendants inherit, so a component that genuinely needs
|
|
44
|
+
* `content-box` for a subtree (an image map, say) can opt out at one node and have it hold for
|
|
45
|
+
* everything below it.
|
|
46
|
+
*/
|
|
47
|
+
declare const boxSizing: lit.CSSResult;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The `drop` region of every drop target.
|
|
51
|
+
*
|
|
52
|
+
* One rule: **a drop container hosts a chip, it never sizes one.** No `flex: 1`, no stretch, no
|
|
53
|
+
* centring. The chip's box is decided by the chip — in the drag bank, mid-flight as a clone, and
|
|
54
|
+
* once dropped — and a container that centres or stretches it makes the drop the one place where
|
|
55
|
+
* that stops being true. That is felt as the chip jumping the instant it lands.
|
|
56
|
+
*
|
|
57
|
+
* `flex: 0 1 auto` on the children says "intrinsic size, no growing, shrink if you must".
|
|
58
|
+
* `align-items: flex-start` stops the cross axis stretching them, which is the flex default and
|
|
59
|
+
* the reason a chip in a tall dropzone used to grow to fill it.
|
|
60
|
+
*
|
|
61
|
+
* The shrink half used to be 0 as well, on the reading that a drop must not resize a chip. It does
|
|
62
|
+
* not say that. `flex-shrink: 0` does not preserve the chip's size, it preserves the chip's
|
|
63
|
+
* UNCONSTRAINED max-content size — and those are the same number only while the bank is wide enough
|
|
64
|
+
* to give the chip its full width. Once the bank is narrower, the bank chip (`flex: 0 1 auto`, like
|
|
65
|
+
* any flex item) shrinks and wraps, the placed chip does not, and the two disagree.
|
|
66
|
+
*
|
|
67
|
+
* Measured on the order fixture, bank vs placed: 209x36 / 209x36 at a 480px interaction, but
|
|
68
|
+
* 204x55 / 209x36 at 440px and 154x55 / 209x36 at 340px — the placed chip a fixed 209px wide,
|
|
69
|
+
* overflowing a drop that had correctly reserved 156px for it. It reached CI as a font difference:
|
|
70
|
+
* the same label measures ~227px under the Linux fonts, which puts 480px on the wrong side of the
|
|
71
|
+
* boundary too.
|
|
72
|
+
*
|
|
73
|
+
* Shrinking cannot make a placed chip smaller than the reservation, because the reservation IS the
|
|
74
|
+
* measured chip (`--qti-dropzone-min-*`, applied to this region's content box). So the floor a
|
|
75
|
+
* shrink stops at is the chip's own size, which is the rule above, not an exception to it.
|
|
76
|
+
* `flex-grow: 0` is what actually prevents the stretch this block cares about, and it is untouched.
|
|
77
|
+
*
|
|
78
|
+
* A container that wants a *minimum* size still declares one (see `--qti-dropzone-min-*`). That
|
|
79
|
+
* sizes the container, never its contents.
|
|
80
|
+
*
|
|
81
|
+
* Verified by drag-drop.invariance.spec.ts: "a chip is the same chip wherever it lives" — including
|
|
82
|
+
* the narrow-bank order fixture, which is there because the wide one passes either way.
|
|
83
|
+
*/
|
|
84
|
+
declare const dropRegion: lit.CSSResult;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The `#validation-message` region every interaction renders, hidden until there is a message.
|
|
88
|
+
*
|
|
89
|
+
* `Interaction` owns showing and hiding it: `reportValidity` sets
|
|
90
|
+
* `display: block !important` inline when `_internals.validationMessage` is non-empty, and clears the
|
|
91
|
+
* text and sets `display: none` inline again when it is not (see interaction.ts). An inline
|
|
92
|
+
* `!important` beats this rule, so the JS still wins whenever it speaks.
|
|
93
|
+
*
|
|
94
|
+
* What it did not own was the *initial* state. Before first validation the div has no inline style,
|
|
95
|
+
* so it needs a stylesheet to start hidden — otherwise it paints as an empty box with the theme's
|
|
96
|
+
* warning border and padding. That hide used to be a `display: none` inside the theme's
|
|
97
|
+
* `validation-message` mixin, which made a document stylesheet responsible for a shadow node's box.
|
|
98
|
+
* Two components had already noticed and worked around it with `style="display:none"` in their
|
|
99
|
+
* template; the other five relied on the theme.
|
|
100
|
+
*
|
|
101
|
+
* The theme still paints the message — colour, border, padding, radius — via `::part(message)`.
|
|
102
|
+
*/
|
|
103
|
+
declare const validationMessage: lit.CSSResult;
|
|
104
|
+
|
|
105
|
+
declare class ScoringHelper {
|
|
106
|
+
/**
|
|
107
|
+
* Checks if a given point is within a specified area.
|
|
108
|
+
* @param point The point to test, represented as a string "x y" (e.g., "102 113").
|
|
109
|
+
* @param areaKey The area definition, including shape and coordinates (e.g., "circle,102,113,16").
|
|
110
|
+
* @param baseType The base type of the response, must be "point" for this method to proceed.
|
|
111
|
+
* @returns True if the point is within the area; false otherwise.
|
|
112
|
+
*/
|
|
113
|
+
static isPointInArea(point: string, areaKey: string, baseType: string): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Checks if a point is inside a polygon using the ray-casting algorithm.
|
|
116
|
+
* @param point The point to test.
|
|
117
|
+
* @param vertices The vertices of the polygon in order (array of {x, y} objects).
|
|
118
|
+
* @returns True if the point is inside the polygon; false otherwise.
|
|
119
|
+
*/
|
|
120
|
+
static isPointInPolygon(point: {
|
|
121
|
+
x: number;
|
|
122
|
+
y: number;
|
|
123
|
+
}, vertices: {
|
|
124
|
+
x: number;
|
|
125
|
+
y: number;
|
|
126
|
+
}[]): boolean;
|
|
127
|
+
static compareSingleValues(value1: Readonly<string>, value2: Readonly<string>, baseType: BaseType): boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
declare const decimalSeparator: () => string;
|
|
131
|
+
declare const convertNumberToUniversalFormat: (number: number | string) => string;
|
|
132
|
+
declare function IsNullOrUndefined(value: unknown): boolean;
|
|
133
|
+
declare function removeDoubleSlashes(str: string): string;
|
|
134
|
+
|
|
135
|
+
export { BaseType, ComputedContext, ComputedItemContext, IsNullOrUndefined, ItemContext, ScoringHelper, boxSizing, computedContext, computedItemContext, convertNumberToUniversalFormat, decimalSeparator, dropRegion, itemContext, removeDoubleSlashes, validationMessage };
|
package/dist/base.js
CHANGED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// ../qti-utilities/dist/decorators/watch.js
|
|
2
|
+
function watch(propertyName, options) {
|
|
3
|
+
const resolvedOptions = {
|
|
4
|
+
waitUntilFirstUpdate: false,
|
|
5
|
+
...options
|
|
6
|
+
};
|
|
7
|
+
return (proto, decoratedFnName) => {
|
|
8
|
+
const { update } = proto;
|
|
9
|
+
const watchedProperties = Array.isArray(propertyName) ? propertyName : [propertyName];
|
|
10
|
+
proto.update = function(changedProps) {
|
|
11
|
+
watchedProperties.forEach((property) => {
|
|
12
|
+
const key = property;
|
|
13
|
+
if (changedProps.has(key)) {
|
|
14
|
+
const oldValue = changedProps.get(key);
|
|
15
|
+
const newValue = this[key];
|
|
16
|
+
if (oldValue !== newValue) {
|
|
17
|
+
if (!resolvedOptions.waitUntilFirstUpdate || this.hasUpdated) {
|
|
18
|
+
this[decoratedFnName](oldValue, newValue);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
update.call(this, changedProps);
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ../qti-utilities/dist/directives/spread-args.js
|
|
29
|
+
import { nothing } from "lit/html.js";
|
|
30
|
+
import { directive } from "lit/directive.js";
|
|
31
|
+
import { AsyncDirective } from "lit/async-directive.js";
|
|
32
|
+
var SpreadArgsDirective = class extends AsyncDirective {
|
|
33
|
+
constructor() {
|
|
34
|
+
super(...arguments);
|
|
35
|
+
this.prevData = {};
|
|
36
|
+
}
|
|
37
|
+
render(_spreadData) {
|
|
38
|
+
return nothing;
|
|
39
|
+
}
|
|
40
|
+
update(part, [spreadData]) {
|
|
41
|
+
if (this.element !== part.element) {
|
|
42
|
+
this.element = part.element;
|
|
43
|
+
}
|
|
44
|
+
this.host = part.options?.host || this.element;
|
|
45
|
+
this.apply(spreadData);
|
|
46
|
+
this.groom(spreadData);
|
|
47
|
+
this.prevData = { ...spreadData };
|
|
48
|
+
return nothing;
|
|
49
|
+
}
|
|
50
|
+
apply(data) {
|
|
51
|
+
const filteredData = this.#filterArgs(data);
|
|
52
|
+
const { prevData, element } = this;
|
|
53
|
+
for (const key in filteredData) {
|
|
54
|
+
const value = filteredData[key];
|
|
55
|
+
if (value === prevData[key]) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (value === true) {
|
|
59
|
+
element.setAttribute(key, "");
|
|
60
|
+
} else if (value === false) {
|
|
61
|
+
element.removeAttribute(key);
|
|
62
|
+
} else if (value != null) {
|
|
63
|
+
element.setAttribute(key, String(value));
|
|
64
|
+
} else {
|
|
65
|
+
element.removeAttribute(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
groom(data) {
|
|
70
|
+
const { prevData, element } = this;
|
|
71
|
+
if (!prevData)
|
|
72
|
+
return;
|
|
73
|
+
const filteredData = data ? this.#filterArgs(data) : {};
|
|
74
|
+
for (const key in prevData) {
|
|
75
|
+
if (!filteredData || !(key in filteredData)) {
|
|
76
|
+
element.removeAttribute(key);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
#filterArgs(args) {
|
|
81
|
+
return Object.fromEntries(Object.entries(args).filter(([key, value]) => {
|
|
82
|
+
if (typeof value === "function") {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (key === "max-choices" && value === 0) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (value === "" || value === null || value === void 0) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var spreadArgs = directive(SpreadArgsDirective);
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
watch
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=chunk-4DB4SC5K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../qti-utilities/src/decorators/watch.ts","../../qti-utilities/src/directives/spread-args.ts"],"sourcesContent":["import type { LitElement } from 'lit';\n\ntype UpdateHandler = (prev?: unknown, next?: unknown) => void;\n\ntype NonUndefined<A> = A extends undefined ? never : A;\n\nexport type UpdateHandlerFunctionKeys<T extends object> = {\n [K in keyof T]-?: NonUndefined<T[K]> extends UpdateHandler ? K : never;\n}[keyof T];\n\ninterface WatchOptions {\n /**\n * If true, will only start watching after the initial update/render\n */\n waitUntilFirstUpdate?: boolean;\n}\n\n/**\n * Runs when observed properties change, e.g. @property or @state, but before the component updates. To wait for an\n * update to complete after a change occurs, use `await this.updateComplete` in the handler. To start watching after the\n * initial update/render, use `{ waitUntilFirstUpdate: true }` or `this.hasUpdated` in the handler.\n *\n * Usage:\n *\n * @watch('propName')\n * handlePropChange(oldValue, newValue) {\n * ...\n * }\n */\nexport function watch(propertyName: string | string[], options?: WatchOptions) {\n const resolvedOptions: Required<WatchOptions> = {\n waitUntilFirstUpdate: false,\n ...options\n };\n return <ElemClass extends LitElement>(\n proto: ElemClass,\n decoratedFnName: UpdateHandlerFunctionKeys<ElemClass> | any\n ) => {\n // @ts-expect-error - update is a protected property\n const { update } = proto;\n const watchedProperties = Array.isArray(propertyName) ? propertyName : [propertyName];\n\n // @ts-expect-error - update is a protected property\n proto.update = function (this: ElemClass, changedProps: Map<keyof ElemClass, ElemClass[keyof ElemClass]>) {\n watchedProperties.forEach(property => {\n const key = property as keyof ElemClass;\n if (changedProps.has(key)) {\n const oldValue = changedProps.get(key);\n const newValue = this[key];\n\n if (oldValue !== newValue) {\n if (!resolvedOptions.waitUntilFirstUpdate || this.hasUpdated) {\n ((this as any)[decoratedFnName] as unknown as UpdateHandler)(oldValue, newValue);\n }\n }\n }\n });\n\n update.call(this, changedProps);\n };\n };\n}\n","import { nothing } from 'lit/html.js';\nimport { directive } from 'lit/directive.js';\nimport { AsyncDirective } from 'lit/async-directive.js';\n\nimport type { ElementPart, Part } from 'lit';\n\n/**\n * A directive that spreads arguments to HTML attributes with intelligent filtering.\n *\n * Features:\n * - Includes `max-choices=\"0\"` even though 0 is falsy\n * - Excludes boolean attributes with no value, empty string, null, or undefined\n * - Excludes event handlers and functions\n *\n * Based on the @open-wc/lit-helpers spread directive implementation.\n */\nexport class SpreadArgsDirective extends AsyncDirective {\n host!: EventTarget | object | Element;\n element!: Element;\n prevData: { [key: string]: unknown } = {};\n\n render(_spreadData: { [key: string]: unknown }) {\n return nothing;\n }\n\n update(part: Part, [spreadData]: Parameters<this['render']>) {\n if (this.element !== (part as ElementPart).element) {\n this.element = (part as ElementPart).element;\n }\n this.host = part.options?.host || this.element;\n this.apply(spreadData);\n this.groom(spreadData);\n this.prevData = { ...spreadData };\n return nothing;\n }\n\n apply(data: { [key: string]: unknown }) {\n // Filter the data according to our rules\n const filteredData = this.#filterArgs(data);\n\n const { prevData, element } = this;\n for (const key in filteredData) {\n const value = filteredData[key];\n if (value === prevData[key]) {\n continue;\n }\n\n // Handle different attribute types\n if (value === true) {\n // Boolean attribute - just set the attribute name\n element.setAttribute(key, '');\n } else if (value === false) {\n // Remove false boolean attributes\n element.removeAttribute(key);\n } else if (value != null) {\n // Standard attribute with value\n element.setAttribute(key, String(value));\n } else {\n // Remove null/undefined attributes\n element.removeAttribute(key);\n }\n }\n }\n\n groom(data: { [key: string]: unknown }) {\n const { prevData, element } = this;\n if (!prevData) return;\n\n // Filter the current data to see what we should have\n const filteredData = data ? this.#filterArgs(data) : {};\n\n for (const key in prevData) {\n if (!filteredData || !(key in filteredData)) {\n // Remove attributes that are no longer present in filtered data\n element.removeAttribute(key);\n }\n }\n }\n\n #filterArgs(args: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(args).filter(([key, value]) => {\n // Always exclude functions (events/functions)\n if (typeof value === 'function') {\n return false;\n }\n\n // Special case for max-choices: include when value is 0\n if (key === 'max-choices' && value === 0) {\n return true;\n }\n\n // Exclude empty strings, null, and undefined\n if (value === '' || value === null || value === undefined) {\n return false;\n }\n\n // Include all other truthy values\n return true;\n })\n );\n }\n}\n\n/**\n * A directive that spreads arguments to HTML attributes with intelligent filtering.\n *\n * @param args - Object containing key-value pairs to spread as attributes\n *\n * @example\n * ```typescript\n * import { spreadArgs } from '@qti-components/utilities';\n *\n * const args = {\n * 'max-choices': 0, // Will be included\n * disabled: true, // Will be included as disabled=\"\"\n * hidden: false, // Will be excluded\n * title: '', // Will be excluded\n * onClick: () => {}, // Will be excluded\n * id: 'my-element' // Will be included as id=\"my-element\"\n * };\n *\n * html`<my-element ${spreadArgs(args)}></my-element>`;\n * ```\n */\nexport const spreadArgs = directive(SpreadArgsDirective);\n\n/**\n * Helper function version that works with @open-wc/lit-helpers spread directive.\n * @deprecated Use the spreadArgs directive directly instead.\n */\nexport function filterArgsForSpread(args: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(args).filter(([, value]) => {\n if (typeof value === 'function') return false;\n if (value === '' || value === null || value === undefined) return false;\n return true;\n })\n );\n}\n"],"mappings":";AA6BM,SAAU,MAAM,cAAiC,SAAsB;AAC3E,QAAM,kBAA0C;IAC9C,sBAAsB;IACtB,GAAG;;AAEL,SAAO,CACL,OACA,oBACE;AAEF,UAAM,EAAE,OAAM,IAAK;AACnB,UAAM,oBAAoB,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC,YAAY;AAGpF,UAAM,SAAS,SAA2B,cAA8D;AACtG,wBAAkB,QAAQ,cAAW;AACnC,cAAM,MAAM;AACZ,YAAI,aAAa,IAAI,GAAG,GAAG;AACzB,gBAAM,WAAW,aAAa,IAAI,GAAG;AACrC,gBAAM,WAAW,KAAK,GAAG;AAEzB,cAAI,aAAa,UAAU;AACzB,gBAAI,CAAC,gBAAgB,wBAAwB,KAAK,YAAY;AAC1D,mBAAa,eAAe,EAA+B,UAAU,QAAQ;YACjF;UACF;QACF;MACF,CAAC;AAED,aAAO,KAAK,MAAM,YAAY;IAChC;EACF;AACF;;;AC7DA,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAczB,IAAO,sBAAP,cAAmC,eAAc;EAAvD,cAAA;;AAGE,SAAA,WAAuC,CAAA;EAmFzC;EAjFE,OAAO,aAAuC;AAC5C,WAAO;EACT;EAEA,OAAO,MAAY,CAAC,UAAU,GAA6B;AACzD,QAAI,KAAK,YAAa,KAAqB,SAAS;AAClD,WAAK,UAAW,KAAqB;IACvC;AACA,SAAK,OAAO,KAAK,SAAS,QAAQ,KAAK;AACvC,SAAK,MAAM,UAAU;AACrB,SAAK,MAAM,UAAU;AACrB,SAAK,WAAW,EAAE,GAAG,WAAU;AAC/B,WAAO;EACT;EAEA,MAAM,MAAgC;AAEpC,UAAM,eAAe,KAAK,YAAY,IAAI;AAE1C,UAAM,EAAE,UAAU,QAAO,IAAK;AAC9B,eAAW,OAAO,cAAc;AAC9B,YAAM,QAAQ,aAAa,GAAG;AAC9B,UAAI,UAAU,SAAS,GAAG,GAAG;AAC3B;MACF;AAGA,UAAI,UAAU,MAAM;AAElB,gBAAQ,aAAa,KAAK,EAAE;MAC9B,WAAW,UAAU,OAAO;AAE1B,gBAAQ,gBAAgB,GAAG;MAC7B,WAAW,SAAS,MAAM;AAExB,gBAAQ,aAAa,KAAK,OAAO,KAAK,CAAC;MACzC,OAAO;AAEL,gBAAQ,gBAAgB,GAAG;MAC7B;IACF;EACF;EAEA,MAAM,MAAgC;AACpC,UAAM,EAAE,UAAU,QAAO,IAAK;AAC9B,QAAI,CAAC;AAAU;AAGf,UAAM,eAAe,OAAO,KAAK,YAAY,IAAI,IAAI,CAAA;AAErD,eAAW,OAAO,UAAU;AAC1B,UAAI,CAAC,gBAAgB,EAAE,OAAO,eAAe;AAE3C,gBAAQ,gBAAgB,GAAG;MAC7B;IACF;EACF;EAEA,YAAY,MAA6B;AACvC,WAAO,OAAO,YACZ,OAAO,QAAQ,IAAI,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,MAAK;AAE3C,UAAI,OAAO,UAAU,YAAY;AAC/B,eAAO;MACT;AAGA,UAAI,QAAQ,iBAAiB,UAAU,GAAG;AACxC,eAAO;MACT;AAGA,UAAI,UAAU,MAAM,UAAU,QAAQ,UAAU,QAAW;AACzD,eAAO;MACT;AAGA,aAAO;IACT,CAAC,CAAC;EAEN;;AAwBK,IAAM,aAAa,UAAU,mBAAmB;","names":[]}
|