@qti-components/graphic-gap-match-interaction 1.1.7 → 2.0.0-next.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.
- package/dist/qti-graphic-gap-match-interaction.d.ts +6 -1
- package/dist/qti-graphic-gap-match-interaction.js +9 -4
- package/dist/qti-graphic-gap-match-interaction.js.map +1 -1
- package/dist/qti-graphic-gap-match-interaction.styles.js +10 -6
- package/dist/qti-graphic-gap-match-interaction.styles.js.map +1 -1
- package/package.json +3 -3
|
@@ -9,12 +9,17 @@ declare const QtiGraphicGapMatchInteraction_base: (abstract new (...args: any[])
|
|
|
9
9
|
* @slot - Default slot for the base image and hotspots.
|
|
10
10
|
*
|
|
11
11
|
* @csspart image - Wrapper around the image slot.
|
|
12
|
-
* @csspart
|
|
12
|
+
* @csspart bank - The slot holding the unplaced chips (was `drags`).
|
|
13
13
|
* @csspart message - Live validation message region (role="alert").
|
|
14
|
+
* @cssstate disabled - The interaction is switched off.
|
|
15
|
+
* @cssstate readonly - The interaction is frozen: it shows its value and takes no input.
|
|
16
|
+
* @cssstate drag-active - A drag is in progress (was `dragzone-active`, `dragzone-enabled`).
|
|
14
17
|
*/
|
|
15
18
|
export declare class QtiGraphicGapMatchInteraction extends QtiGraphicGapMatchInteraction_base {
|
|
16
19
|
#private;
|
|
17
20
|
static styles: CSSResultGroup;
|
|
21
|
+
/** QTI's default (information model §5.60). */
|
|
22
|
+
maxAssociations: number;
|
|
18
23
|
/**
|
|
19
24
|
* A hotspot's box is its authored `coords`, always (CONFIGURABILITY.md §4). The measured
|
|
20
25
|
* `--qti-dropzone-min-*` reservation is a floor on the hotspot's `min-width`/`min-height`, and it
|
|
@@ -10,7 +10,7 @@ import { Interaction } from '@qti-components/base';
|
|
|
10
10
|
import { positionShapes } from '@qti-components/interactions-core/internal/hotspots/hotspot';
|
|
11
11
|
import { DragDropSlottedMixin, DragDropSlottedSortableMixin } from '@qti-components/interactions-core/mixins/drag-drop-observables';
|
|
12
12
|
import styles from './qti-graphic-gap-match-interaction.styles';
|
|
13
|
-
const SlottedBase = DragDropSlottedMixin(Interaction, 'qti-gap-img, qti-gap-text', 'qti-associable-hotspot', `slot[part~='
|
|
13
|
+
const SlottedBase = DragDropSlottedMixin(Interaction, 'qti-gap-img, qti-gap-text', 'qti-associable-hotspot', `slot[part~='bank']`);
|
|
14
14
|
/**
|
|
15
15
|
* Graphic gap-match interaction: candidates drag choices onto image hotspots.
|
|
16
16
|
*
|
|
@@ -19,12 +19,17 @@ const SlottedBase = DragDropSlottedMixin(Interaction, 'qti-gap-img, qti-gap-text
|
|
|
19
19
|
* @slot - Default slot for the base image and hotspots.
|
|
20
20
|
*
|
|
21
21
|
* @csspart image - Wrapper around the image slot.
|
|
22
|
-
* @csspart
|
|
22
|
+
* @csspart bank - The slot holding the unplaced chips (was `drags`).
|
|
23
23
|
* @csspart message - Live validation message region (role="alert").
|
|
24
|
+
* @cssstate disabled - The interaction is switched off.
|
|
25
|
+
* @cssstate readonly - The interaction is frozen: it shows its value and takes no input.
|
|
26
|
+
* @cssstate drag-active - A drag is in progress (was `dragzone-active`, `dragzone-enabled`).
|
|
24
27
|
*/
|
|
25
|
-
export class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(SlottedBase, '[
|
|
28
|
+
export class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(SlottedBase, '[data-draggable="true"]') {
|
|
26
29
|
constructor() {
|
|
27
30
|
super(...arguments);
|
|
31
|
+
/** QTI's default (information model §5.60). */
|
|
32
|
+
this.maxAssociations = 1;
|
|
28
33
|
/**
|
|
29
34
|
* A hotspot's box is its authored `coords`, always (CONFIGURABILITY.md §4). The measured
|
|
30
35
|
* `--qti-dropzone-min-*` reservation is a floor on the hotspot's `min-width`/`min-height`, and it
|
|
@@ -52,7 +57,7 @@ export class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(
|
|
|
52
57
|
const scale = `--qti-graphic-scale: ${this._graphicScale}`;
|
|
53
58
|
return html ` <slot name="prompt"></slot>
|
|
54
59
|
<slot part="image" style=${scale}></slot>
|
|
55
|
-
<slot part="drags" name="drags" class="hover-border" style=${scale}></slot>
|
|
60
|
+
<slot part="bank drags" name="drags" class="hover-border" style=${scale}></slot>
|
|
56
61
|
<div role="alert" part="message" id="validation-message"></div>`;
|
|
57
62
|
}
|
|
58
63
|
shouldReturnToInventoryOnInventoryDrop() {
|
|
@@ -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';\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"]}
|
|
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~='bank']`\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 bank - The slot holding the unplaced chips (was `drags`).\n * @csspart message - Live validation message region (role=\"alert\").\n * @cssstate disabled - The interaction is switched off.\n * @cssstate readonly - The interaction is frozen: it shows its value and takes no input.\n * @cssstate drag-active - A drag is in progress (was `dragzone-active`, `dragzone-enabled`).\n */\nexport class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(\n SlottedBase,\n '[data-draggable=\"true\"]'\n) {\n static override styles: CSSResultGroup = styles;\n\n /** QTI's default (information model §5.60). */\n public override maxAssociations = 1;\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=\"bank 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"]}
|
|
@@ -59,15 +59,19 @@ export default [
|
|
|
59
59
|
}
|
|
60
60
|
/* [part='drops'] , */
|
|
61
61
|
|
|
62
|
-
[
|
|
62
|
+
[part~='bank'] {
|
|
63
|
+
/* The bank, the same in every drag-drop interaction: flush with the container (no padding, no
|
|
64
|
+
margin, no edge of its own), chips a --qti-gap apart. Its lit and ringed looks while a chip is
|
|
65
|
+
in flight are the theme's ('::part(bank drag-active)', '::part(bank drag-over)'). */
|
|
63
66
|
display: flex;
|
|
64
|
-
align-items: flex-start;
|
|
65
67
|
flex-wrap: wrap;
|
|
68
|
+
align-items: flex-start;
|
|
66
69
|
flex: 1;
|
|
67
|
-
|
|
68
|
-
padding: 0
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
gap: var(--qti-gap, 0.5rem);
|
|
71
|
+
padding: 0;
|
|
72
|
+
margin: 0;
|
|
73
|
+
border: 0;
|
|
74
|
+
border-radius: var(--qti-radius-box, 0.2rem);
|
|
71
75
|
}
|
|
72
76
|
/*
|
|
73
77
|
* Fluid, like the hotspot interaction's graphic: an item declares the graphic at the size its
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qti-graphic-gap-match-interaction.styles.js","sourceRoot":"","sources":["../src/qti-graphic-gap-match-interaction.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEpE,eAAe;IACb,SAAS;IACT,iBAAiB;IACjB,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"qti-graphic-gap-match-interaction.styles.js","sourceRoot":"","sources":["../src/qti-graphic-gap-match-interaction.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEpE,eAAe;IACb,SAAS;IACT,iBAAiB;IACjB,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFF;CACF,CAAC","sourcesContent":["import { css } from 'lit';\n\nimport { boxSizing, validationMessage } from '@qti-components/base';\n\nexport default [\n boxSizing,\n validationMessage,\n css`\n :host {\n display: flex;\n align-items: flex-start;\n flex-direction: column;\n flex-wrap: wrap;\n gap: 0.5rem;\n /* Containing block for the absolutely positioned hotspots over the image. Was set on this\n host from the theme. */\n position: relative;\n }\n\n :host(.qti-choices-top) {\n flex-direction: column-reverse;\n }\n :host(.qti-choices-bottom) {\n flex-direction: column;\n }\n :host(.qti-choices-left) {\n flex-direction: row-reverse;\n & [name='drags'] {\n width: 25%;\n }\n & [part='image'] {\n /* A cap, not a width: the slot must stay exactly the graphic's box (see below). */\n max-width: 75%;\n }\n }\n :host(.qti-choices-right) {\n flex-direction: row;\n & [name='drags'] {\n width: 25%;\n }\n & [part='image'] {\n /* A cap, not a width: the slot must stay exactly the graphic's box (see below). */\n max-width: 75%;\n }\n }\n /*\n * The coordinate space the drop areas sit on. Every qti-associable-hotspot is position:absolute\n * and placed with left/top percentages of the graphic (positionShapes), so this slot must be\n * (a) a positioning context and (b) exactly the box of the graphic — the same contract as the\n * hotspot interaction's slot.\n *\n * width: fit-content shrink-wraps the slot to the graphic instead of the full row, so a\n * percentage of the slot is a percentage of the picture.\n * max-width: 100% lets the slot, and with it the graphic, narrow to the container.\n */\n [part='image'] {\n display: block;\n position: relative;\n width: fit-content;\n max-width: 100%;\n }\n /* [part='drops'] , */\n\n [part~='bank'] {\n /* The bank, the same in every drag-drop interaction: flush with the container (no padding, no\n margin, no edge of its own), chips a --qti-gap apart. Its lit and ringed looks while a chip is\n in flight are the theme's ('::part(bank drag-active)', '::part(bank drag-over)'). */\n display: flex;\n flex-wrap: wrap;\n align-items: flex-start;\n flex: 1;\n gap: var(--qti-gap, 0.5rem);\n padding: 0;\n margin: 0;\n border: 0;\n border-radius: var(--qti-radius-box, 0.2rem);\n }\n /*\n * Fluid, like the hotspot interaction's graphic: an item declares the graphic at the size its\n * coords were authored in (often a full-resolution picture, 1600px wide), and it must still fit a\n * column or a phone. The drop areas are percentages of it, so they scale along.\n *\n * max-width: 100% shrinks a graphic wider than its container, never upscales a small one.\n * height: auto keeps the aspect ratio the width/height attributes declare; without it the\n * height attribute pins the height while the width shrinks.\n * display: block no inline descender gap under the picture, which would make the slot\n * taller than the graphic and skew every vertical percentage.\n */\n ::slotted(img),\n ::slotted(object) {\n display: block;\n max-width: 100%;\n height: auto;\n user-select: none;\n pointer-events: none;\n }\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": "
|
|
4
|
+
"version": "2.0.0-next.7",
|
|
5
5
|
"author": "",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@qti-components/base": "^
|
|
8
|
-
"@qti-components/interactions-core": "^
|
|
7
|
+
"@qti-components/base": "^3.0.0-next.6",
|
|
8
|
+
"@qti-components/interactions-core": "^3.0.0-next.7",
|
|
9
9
|
"@qti-components/utilities": "^1.3.3"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|