@qti-components/graphic-gap-match-interaction 1.1.6 → 1.1.7
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.
|
@@ -15,6 +15,20 @@ declare const QtiGraphicGapMatchInteraction_base: (abstract new (...args: any[])
|
|
|
15
15
|
export declare class QtiGraphicGapMatchInteraction extends QtiGraphicGapMatchInteraction_base {
|
|
16
16
|
#private;
|
|
17
17
|
static styles: CSSResultGroup;
|
|
18
|
+
/**
|
|
19
|
+
* A hotspot's box is its authored `coords`, always (CONFIGURABILITY.md §4). The measured
|
|
20
|
+
* `--qti-dropzone-min-*` reservation is a floor on the hotspot's `min-width`/`min-height`, and it
|
|
21
|
+
* beat the percentage box from `positionShapes` as soon as the graphic rendered narrower than its
|
|
22
|
+
* declared width: a 275px chip kept a 17% hotspot at 275px over a 482px map. Chips are sized to
|
|
23
|
+
* their hotspots through `--qti-graphic-scale` instead.
|
|
24
|
+
*/
|
|
25
|
+
autoSizeDropzones: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Rendered width of the graphic over its declared width: the factor the drop areas are scaled by.
|
|
28
|
+
* Published as `--qti-graphic-scale` on both slots, so it reaches a chip in the bank and one placed
|
|
29
|
+
* in a hotspot (qti-gap-img.styles.ts), without writing to the light-DOM host.
|
|
30
|
+
*/
|
|
31
|
+
private _graphicScale;
|
|
18
32
|
render(): import("lit").TemplateResult<1>;
|
|
19
33
|
shouldReturnToInventoryOnInventoryDrop(): boolean;
|
|
20
34
|
connectedCallback(): void;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
1
7
|
import { html } from 'lit';
|
|
8
|
+
import { state } from 'lit/decorators.js';
|
|
2
9
|
import { Interaction } from '@qti-components/base';
|
|
3
10
|
import { positionShapes } from '@qti-components/interactions-core/internal/hotspots/hotspot';
|
|
4
11
|
import { DragDropSlottedMixin, DragDropSlottedSortableMixin } from '@qti-components/interactions-core/mixins/drag-drop-observables';
|
|
@@ -16,11 +23,36 @@ const SlottedBase = DragDropSlottedMixin(Interaction, 'qti-gap-img, qti-gap-text
|
|
|
16
23
|
* @csspart message - Live validation message region (role="alert").
|
|
17
24
|
*/
|
|
18
25
|
export class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(SlottedBase, '[qti-draggable="true"]') {
|
|
26
|
+
constructor() {
|
|
27
|
+
super(...arguments);
|
|
28
|
+
/**
|
|
29
|
+
* A hotspot's box is its authored `coords`, always (CONFIGURABILITY.md §4). The measured
|
|
30
|
+
* `--qti-dropzone-min-*` reservation is a floor on the hotspot's `min-width`/`min-height`, and it
|
|
31
|
+
* beat the percentage box from `positionShapes` as soon as the graphic rendered narrower than its
|
|
32
|
+
* declared width: a 275px chip kept a 17% hotspot at 275px over a 482px map. Chips are sized to
|
|
33
|
+
* their hotspots through `--qti-graphic-scale` instead.
|
|
34
|
+
*/
|
|
35
|
+
this.autoSizeDropzones = false;
|
|
36
|
+
/**
|
|
37
|
+
* Rendered width of the graphic over its declared width: the factor the drop areas are scaled by.
|
|
38
|
+
* Published as `--qti-graphic-scale` on both slots, so it reaches a chip in the bank and one placed
|
|
39
|
+
* in a hotspot (qti-gap-img.styles.ts), without writing to the light-DOM host.
|
|
40
|
+
*/
|
|
41
|
+
this._graphicScale = 1;
|
|
42
|
+
this.#graphicObserver = null;
|
|
43
|
+
this.#observedGraphic = null;
|
|
44
|
+
this.#positionHotspotOnRegister = (e) => {
|
|
45
|
+
this.#positionHotspot(e.target);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
19
48
|
static { this.styles = styles; }
|
|
49
|
+
#graphicObserver;
|
|
50
|
+
#observedGraphic;
|
|
20
51
|
render() {
|
|
52
|
+
const scale = `--qti-graphic-scale: ${this._graphicScale}`;
|
|
21
53
|
return html ` <slot name="prompt"></slot>
|
|
22
|
-
<slot part="image"></slot>
|
|
23
|
-
<slot part="drags" name="drags" class="hover-border"></slot>
|
|
54
|
+
<slot part="image" style=${scale}></slot>
|
|
55
|
+
<slot part="drags" name="drags" class="hover-border" style=${scale}></slot>
|
|
24
56
|
<div role="alert" part="message" id="validation-message"></div>`;
|
|
25
57
|
}
|
|
26
58
|
shouldReturnToInventoryOnInventoryDrop() {
|
|
@@ -68,11 +100,31 @@ export class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(
|
|
|
68
100
|
if (!graphic || !coords || !shape)
|
|
69
101
|
return;
|
|
70
102
|
this.#keepDeclaredRatio(graphic);
|
|
103
|
+
this.#observeGraphic(graphic);
|
|
71
104
|
positionShapes(shape, coords.split(',').map(s => parseFloat(s)), graphic, hotspot);
|
|
72
105
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Tracks the graphic's rendered width. The authored chip sizes live in the same coordinate space
|
|
108
|
+
* as the `coords` — an item that draws a 275x126 hotspot for a 275x126 tile means the tile to fill
|
|
109
|
+
* it — so the chips scale by the same factor as the drop areas.
|
|
110
|
+
*/
|
|
111
|
+
#observeGraphic(graphic) {
|
|
112
|
+
if (this.#observedGraphic === graphic)
|
|
113
|
+
return;
|
|
114
|
+
this.#graphicObserver?.disconnect();
|
|
115
|
+
this.#observedGraphic = graphic;
|
|
116
|
+
this.#graphicObserver = new ResizeObserver(() => {
|
|
117
|
+
const declared = parseFloat(graphic.getAttribute('width') ?? '');
|
|
118
|
+
const rendered = graphic.getBoundingClientRect().width;
|
|
119
|
+
const scale = declared > 0 && rendered > 0 ? Math.min(1, rendered / declared) : 1;
|
|
120
|
+
// Rounded, so sub-pixel jitter does not re-render on every observation.
|
|
121
|
+
const rounded = Math.round(scale * 1000) / 1000;
|
|
122
|
+
if (rounded !== this._graphicScale)
|
|
123
|
+
this._graphicScale = rounded;
|
|
124
|
+
});
|
|
125
|
+
this.#graphicObserver.observe(graphic);
|
|
126
|
+
}
|
|
127
|
+
#positionHotspotOnRegister;
|
|
76
128
|
connectedCallback() {
|
|
77
129
|
super.connectedCallback();
|
|
78
130
|
this.addEventListener('qti-register-hotspot', this.#positionHotspotOnRegister);
|
|
@@ -83,6 +135,12 @@ export class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(
|
|
|
83
135
|
disconnectedCallback() {
|
|
84
136
|
super.disconnectedCallback();
|
|
85
137
|
this.removeEventListener('qti-register-hotspot', this.#positionHotspotOnRegister);
|
|
138
|
+
this.#graphicObserver?.disconnect();
|
|
139
|
+
this.#graphicObserver = null;
|
|
140
|
+
this.#observedGraphic = null;
|
|
86
141
|
}
|
|
87
142
|
}
|
|
143
|
+
__decorate([
|
|
144
|
+
state()
|
|
145
|
+
], QtiGraphicGapMatchInteraction.prototype, "_graphicScale", void 0);
|
|
88
146
|
//# sourceMappingURL=qti-graphic-gap-match-interaction.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qti-graphic-gap-match-interaction.js","sourceRoot":"","sources":["../src/qti-graphic-gap-match-interaction.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit';\n\nimport { Interaction } from '@qti-components/base';\nimport { positionShapes } from '@qti-components/interactions-core/internal/hotspots/hotspot';\nimport {\n DragDropSlottedMixin,\n DragDropSlottedSortableMixin\n} from '@qti-components/interactions-core/mixins/drag-drop-observables';\n\nimport styles from './qti-graphic-gap-match-interaction.styles';\n\nimport type { Graphic } from '@qti-components/interactions-core/internal/hotspots/hotspot';\nimport type { CSSResultGroup } from 'lit';\nconst SlottedBase = DragDropSlottedMixin(\n Interaction,\n 'qti-gap-img, qti-gap-text',\n 'qti-associable-hotspot',\n `slot[part~='drags']`\n);\n\n/**\n * Graphic gap-match interaction: candidates drag choices onto image hotspots.\n *\n * @slot prompt - The prompt shown above the image.\n * @slot drags - The draggable choice sources.\n * @slot - Default slot for the base image and hotspots.\n *\n * @csspart image - Wrapper around the image slot.\n * @csspart drags - Wrapper around the drag sources slot.\n * @csspart message - Live validation message region (role=\"alert\").\n */\nexport class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(SlottedBase, '[qti-draggable=\"true\"]') {\n static override styles: CSSResultGroup = styles;\n\n override render() {\n return html` <slot name=\"prompt\"></slot>\n <slot part=\"image\"></slot>\n <slot part=\"drags\" name=\"drags\" class=\"hover-border\"></slot>\n <div role=\"alert\" part=\"message\" id=\"validation-message\"></div>`;\n }\n\n public override shouldReturnToInventoryOnInventoryDrop(): boolean {\n return true;\n }\n\n /**\n * The background graphic: a direct child, as QTI places it. Not `findGraphic(this)`, which\n * searches every descendant and would pick up a `qti-gap-img`'s picture (or one in the prompt)\n * when it comes first.\n */\n #graphic(): Graphic | null {\n return this.querySelector<Graphic>(':scope > img, :scope > object[type^=\"image\"]');\n }\n\n /**\n * Pins the graphic's box to the aspect ratio its width/height attributes declare: the coordinate\n * space the drop areas are percentages of. With the fluid `height: auto` (see the styles) the box\n * would otherwise follow the bitmap — an `<object>` gets no ratio from its attributes at all, and\n * an `<img>` only `auto w / h`, where the loaded bitmap's own ratio wins. Either way a picture\n * whose pixels differ from its declared size would slide out from under its drop areas. Pinned,\n * it fills the declared box as it does at its fixed size.\n */\n #keepDeclaredRatio(graphic: Graphic): void {\n if (graphic.style.aspectRatio) return;\n const length = (name: 'width' | 'height') => {\n const raw = (graphic.getAttribute(name) ?? '').trim();\n const value = parseFloat(raw);\n return raw.endsWith('%') || !(value > 0) ? null : value;\n };\n const width = length('width');\n const height = length('height');\n if (width && height) graphic.style.aspectRatio = `${width} / ${height}`;\n }\n\n /**\n * Places a drop area over the graphic with `positionShapes`, the hotspot interaction's helper:\n * percentages of the coordinate space the `coords` were authored in, so the areas scale with the\n * graphic when it is narrower than its declared width (see the styles). They used to be written\n * in pixels, which only held while the graphic rendered at exactly that width.\n */\n #positionHotspot(hotspot: HTMLElement): void {\n const graphic = this.#graphic();\n const coords = hotspot.getAttribute('coords');\n const shape = hotspot.getAttribute('shape');\n if (!graphic || !coords || !shape) return;\n this.#keepDeclaredRatio(graphic);\n positionShapes(\n shape,\n coords.split(',').map(s => parseFloat(s)),\n graphic,\n hotspot\n );\n }\n\n #positionHotspotOnRegister = (e: Event): void => {\n this.#positionHotspot(e.target as HTMLElement);\n };\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('qti-register-hotspot', this.#positionHotspotOnRegister);\n // A hotspot that connected before this element was upgraded has already dispatched its\n // register event (the hotspot interaction has the same catch-up).\n this.querySelectorAll<HTMLElement>(':scope > qti-associable-hotspot').forEach(hotspot =>\n this.#positionHotspot(hotspot)\n );\n }\n override disconnectedCallback() {\n super.disconnectedCallback();\n this.removeEventListener('qti-register-hotspot', this.#positionHotspotOnRegister);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'qti-graphic-gap-match-interaction': QtiGraphicGapMatchInteraction;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"qti-graphic-gap-match-interaction.js","sourceRoot":"","sources":["../src/qti-graphic-gap-match-interaction.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit';\nimport { state } from 'lit/decorators.js';\n\nimport { Interaction } from '@qti-components/base';\nimport { positionShapes } from '@qti-components/interactions-core/internal/hotspots/hotspot';\nimport {\n DragDropSlottedMixin,\n DragDropSlottedSortableMixin\n} from '@qti-components/interactions-core/mixins/drag-drop-observables';\n\nimport styles from './qti-graphic-gap-match-interaction.styles';\n\nimport type { Graphic } from '@qti-components/interactions-core/internal/hotspots/hotspot';\nimport type { CSSResultGroup } from 'lit';\nconst SlottedBase = DragDropSlottedMixin(\n Interaction,\n 'qti-gap-img, qti-gap-text',\n 'qti-associable-hotspot',\n `slot[part~='drags']`\n);\n\n/**\n * Graphic gap-match interaction: candidates drag choices onto image hotspots.\n *\n * @slot prompt - The prompt shown above the image.\n * @slot drags - The draggable choice sources.\n * @slot - Default slot for the base image and hotspots.\n *\n * @csspart image - Wrapper around the image slot.\n * @csspart drags - Wrapper around the drag sources slot.\n * @csspart message - Live validation message region (role=\"alert\").\n */\nexport class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(SlottedBase, '[qti-draggable=\"true\"]') {\n static override styles: CSSResultGroup = styles;\n\n /**\n * A hotspot's box is its authored `coords`, always (CONFIGURABILITY.md §4). The measured\n * `--qti-dropzone-min-*` reservation is a floor on the hotspot's `min-width`/`min-height`, and it\n * beat the percentage box from `positionShapes` as soon as the graphic rendered narrower than its\n * declared width: a 275px chip kept a 17% hotspot at 275px over a 482px map. Chips are sized to\n * their hotspots through `--qti-graphic-scale` instead.\n */\n public override autoSizeDropzones = false;\n\n /**\n * Rendered width of the graphic over its declared width: the factor the drop areas are scaled by.\n * Published as `--qti-graphic-scale` on both slots, so it reaches a chip in the bank and one placed\n * in a hotspot (qti-gap-img.styles.ts), without writing to the light-DOM host.\n */\n @state() private _graphicScale = 1;\n\n #graphicObserver: ResizeObserver | null = null;\n #observedGraphic: Graphic | null = null;\n\n override render() {\n const scale = `--qti-graphic-scale: ${this._graphicScale}`;\n return html` <slot name=\"prompt\"></slot>\n <slot part=\"image\" style=${scale}></slot>\n <slot part=\"drags\" name=\"drags\" class=\"hover-border\" style=${scale}></slot>\n <div role=\"alert\" part=\"message\" id=\"validation-message\"></div>`;\n }\n\n public override shouldReturnToInventoryOnInventoryDrop(): boolean {\n return true;\n }\n\n /**\n * The background graphic: a direct child, as QTI places it. Not `findGraphic(this)`, which\n * searches every descendant and would pick up a `qti-gap-img`'s picture (or one in the prompt)\n * when it comes first.\n */\n #graphic(): Graphic | null {\n return this.querySelector<Graphic>(':scope > img, :scope > object[type^=\"image\"]');\n }\n\n /**\n * Pins the graphic's box to the aspect ratio its width/height attributes declare: the coordinate\n * space the drop areas are percentages of. With the fluid `height: auto` (see the styles) the box\n * would otherwise follow the bitmap — an `<object>` gets no ratio from its attributes at all, and\n * an `<img>` only `auto w / h`, where the loaded bitmap's own ratio wins. Either way a picture\n * whose pixels differ from its declared size would slide out from under its drop areas. Pinned,\n * it fills the declared box as it does at its fixed size.\n */\n #keepDeclaredRatio(graphic: Graphic): void {\n if (graphic.style.aspectRatio) return;\n const length = (name: 'width' | 'height') => {\n const raw = (graphic.getAttribute(name) ?? '').trim();\n const value = parseFloat(raw);\n return raw.endsWith('%') || !(value > 0) ? null : value;\n };\n const width = length('width');\n const height = length('height');\n if (width && height) graphic.style.aspectRatio = `${width} / ${height}`;\n }\n\n /**\n * Places a drop area over the graphic with `positionShapes`, the hotspot interaction's helper:\n * percentages of the coordinate space the `coords` were authored in, so the areas scale with the\n * graphic when it is narrower than its declared width (see the styles). They used to be written\n * in pixels, which only held while the graphic rendered at exactly that width.\n */\n #positionHotspot(hotspot: HTMLElement): void {\n const graphic = this.#graphic();\n const coords = hotspot.getAttribute('coords');\n const shape = hotspot.getAttribute('shape');\n if (!graphic || !coords || !shape) return;\n this.#keepDeclaredRatio(graphic);\n this.#observeGraphic(graphic);\n positionShapes(\n shape,\n coords.split(',').map(s => parseFloat(s)),\n graphic,\n hotspot\n );\n }\n\n /**\n * Tracks the graphic's rendered width. The authored chip sizes live in the same coordinate space\n * as the `coords` — an item that draws a 275x126 hotspot for a 275x126 tile means the tile to fill\n * it — so the chips scale by the same factor as the drop areas.\n */\n #observeGraphic(graphic: Graphic): void {\n if (this.#observedGraphic === graphic) return;\n this.#graphicObserver?.disconnect();\n this.#observedGraphic = graphic;\n this.#graphicObserver = new ResizeObserver(() => {\n const declared = parseFloat(graphic.getAttribute('width') ?? '');\n const rendered = graphic.getBoundingClientRect().width;\n const scale = declared > 0 && rendered > 0 ? Math.min(1, rendered / declared) : 1;\n // Rounded, so sub-pixel jitter does not re-render on every observation.\n const rounded = Math.round(scale * 1000) / 1000;\n if (rounded !== this._graphicScale) this._graphicScale = rounded;\n });\n this.#graphicObserver.observe(graphic);\n }\n\n #positionHotspotOnRegister = (e: Event): void => {\n this.#positionHotspot(e.target as HTMLElement);\n };\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('qti-register-hotspot', this.#positionHotspotOnRegister);\n // A hotspot that connected before this element was upgraded has already dispatched its\n // register event (the hotspot interaction has the same catch-up).\n this.querySelectorAll<HTMLElement>(':scope > qti-associable-hotspot').forEach(hotspot =>\n this.#positionHotspot(hotspot)\n );\n }\n override disconnectedCallback() {\n super.disconnectedCallback();\n this.removeEventListener('qti-register-hotspot', this.#positionHotspotOnRegister);\n this.#graphicObserver?.disconnect();\n this.#graphicObserver = null;\n this.#observedGraphic = null;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'qti-graphic-gap-match-interaction': QtiGraphicGapMatchInteraction;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qti-components/graphic-gap-match-interaction",
|
|
3
3
|
"description": "QTI graphic-gap-match-interaction interaction",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.7",
|
|
5
5
|
"author": "",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@qti-components/base": "^2.
|
|
8
|
-
"@qti-components/interactions-core": "^2.1.
|
|
7
|
+
"@qti-components/base": "^2.5.0",
|
|
8
|
+
"@qti-components/interactions-core": "^2.1.4",
|
|
9
9
|
"@qti-components/utilities": "^1.3.3"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|