@qti-components/graphic-gap-match-interaction 1.1.4 → 1.1.6-next.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.
@@ -0,0 +1,6 @@
1
+ import { QtiGraphicGapMatchInteraction } from './qti-graphic-gap-match-interaction';
2
+ export declare const elements: readonly [{
3
+ readonly tag: "qti-graphic-gap-match-interaction";
4
+ readonly ctor: typeof QtiGraphicGapMatchInteraction;
5
+ }];
6
+ export { QtiGraphicGapMatchInteraction };
@@ -0,0 +1,4 @@
1
+ import { QtiGraphicGapMatchInteraction } from './qti-graphic-gap-match-interaction';
2
+ export const elements = [{ tag: 'qti-graphic-gap-match-interaction', ctor: QtiGraphicGapMatchInteraction }];
3
+ export { QtiGraphicGapMatchInteraction };
4
+ //# sourceMappingURL=elements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elements.js","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AAEpF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EAAE,GAAG,EAAE,mCAAmC,EAAE,IAAI,EAAE,6BAA6B,EAAE,CAAU,CAAC;AAErH,OAAO,EAAE,6BAA6B,EAAE,CAAC","sourcesContent":["import { QtiGraphicGapMatchInteraction } from './qti-graphic-gap-match-interaction';\n\nexport const elements = [{ tag: 'qti-graphic-gap-match-interaction', ctor: QtiGraphicGapMatchInteraction }] as const;\n\nexport { QtiGraphicGapMatchInteraction };\n"]}
@@ -1,6 +1,17 @@
1
1
  import { Interaction } from '@qti-components/base';
2
2
  import type { CSSResultGroup } from 'lit';
3
3
  declare const QtiGraphicGapMatchInteraction_base: (abstract new (...args: any[]) => import("@qti-components/interactions-core/mixins/drag-drop-observables").DragDropSlottedSortable) & (abstract new (...args: any[]) => import("@qti-components/interactions-core/mixins/drag-drop-observables").DragDropSlotted) & typeof Interaction;
4
+ /**
5
+ * Graphic gap-match interaction: candidates drag choices onto image hotspots.
6
+ *
7
+ * @slot prompt - The prompt shown above the image.
8
+ * @slot drags - The draggable choice sources.
9
+ * @slot - Default slot for the base image and hotspots.
10
+ *
11
+ * @csspart image - Wrapper around the image slot.
12
+ * @csspart drags - Wrapper around the drag sources slot.
13
+ * @csspart message - Live validation message region (role="alert").
14
+ */
4
15
  export declare class QtiGraphicGapMatchInteraction extends QtiGraphicGapMatchInteraction_base {
5
16
  #private;
6
17
  static styles: CSSResultGroup;
@@ -1,9 +1,20 @@
1
1
  import { html } from 'lit';
2
2
  import { Interaction } from '@qti-components/base';
3
+ import { positionShapes } from '@qti-components/interactions-core/internal/hotspots/hotspot';
3
4
  import { DragDropSlottedMixin, DragDropSlottedSortableMixin } from '@qti-components/interactions-core/mixins/drag-drop-observables';
4
- // import { DragDropInteractionMixin } from '@qti-components/interactions-core/mixins/drag-drop';
5
5
  import styles from './qti-graphic-gap-match-interaction.styles';
6
- const SlottedBase = DragDropSlottedMixin(Interaction, 'qti-gap-img, qti-gap-text', 'qti-associable-hotspot', `slot[part='drags']`);
6
+ const SlottedBase = DragDropSlottedMixin(Interaction, 'qti-gap-img, qti-gap-text', 'qti-associable-hotspot', `slot[part~='drags']`);
7
+ /**
8
+ * Graphic gap-match interaction: candidates drag choices onto image hotspots.
9
+ *
10
+ * @slot prompt - The prompt shown above the image.
11
+ * @slot drags - The draggable choice sources.
12
+ * @slot - Default slot for the base image and hotspots.
13
+ *
14
+ * @csspart image - Wrapper around the image slot.
15
+ * @csspart drags - Wrapper around the drag sources slot.
16
+ * @csspart message - Live validation message region (role="alert").
17
+ */
7
18
  export class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(SlottedBase, '[qti-draggable="true"]') {
8
19
  static { this.styles = styles; }
9
20
  render() {
@@ -15,37 +26,59 @@ export class QtiGraphicGapMatchInteraction extends DragDropSlottedSortableMixin(
15
26
  shouldReturnToInventoryOnInventoryDrop() {
16
27
  return true;
17
28
  }
18
- #positionHotspotOnRegister(e) {
19
- const hotspot = e.target;
29
+ /**
30
+ * The background graphic: a direct child, as QTI places it. Not `findGraphic(this)`, which
31
+ * searches every descendant and would pick up a `qti-gap-img`'s picture (or one in the prompt)
32
+ * when it comes first.
33
+ */
34
+ #graphic() {
35
+ return this.querySelector(':scope > img, :scope > object[type^="image"]');
36
+ }
37
+ /**
38
+ * Pins the graphic's box to the aspect ratio its width/height attributes declare: the coordinate
39
+ * space the drop areas are percentages of. With the fluid `height: auto` (see the styles) the box
40
+ * would otherwise follow the bitmap — an `<object>` gets no ratio from its attributes at all, and
41
+ * an `<img>` only `auto w / h`, where the loaded bitmap's own ratio wins. Either way a picture
42
+ * whose pixels differ from its declared size would slide out from under its drop areas. Pinned,
43
+ * it fills the declared box as it does at its fixed size.
44
+ */
45
+ #keepDeclaredRatio(graphic) {
46
+ if (graphic.style.aspectRatio)
47
+ return;
48
+ const length = (name) => {
49
+ const raw = (graphic.getAttribute(name) ?? '').trim();
50
+ const value = parseFloat(raw);
51
+ return raw.endsWith('%') || !(value > 0) ? null : value;
52
+ };
53
+ const width = length('width');
54
+ const height = length('height');
55
+ if (width && height)
56
+ graphic.style.aspectRatio = `${width} / ${height}`;
57
+ }
58
+ /**
59
+ * Places a drop area over the graphic with `positionShapes`, the hotspot interaction's helper:
60
+ * percentages of the coordinate space the `coords` were authored in, so the areas scale with the
61
+ * graphic when it is narrower than its declared width (see the styles). They used to be written
62
+ * in pixels, which only held while the graphic rendered at exactly that width.
63
+ */
64
+ #positionHotspot(hotspot) {
65
+ const graphic = this.#graphic();
20
66
  const coords = hotspot.getAttribute('coords');
21
67
  const shape = hotspot.getAttribute('shape');
22
- const coordsNumber = coords.split(',').map(s => parseInt(s));
23
- // positionHotspots(shape, coordsNumber, img, hotspot);
24
- switch (shape) {
25
- case 'circle':
26
- {
27
- const [centerX, centerY, radius] = coordsNumber;
28
- hotspot.style.left = centerX - radius + 'px';
29
- hotspot.style.top = centerY - radius + 'px';
30
- hotspot.style.width = hotspot.style.height = 2 * radius + 'px';
31
- }
32
- break;
33
- case 'rect':
34
- {
35
- const [leftX, topY, rightX, bottomY] = coordsNumber;
36
- hotspot.style.left = leftX + 'px';
37
- hotspot.style.top = topY + 'px';
38
- hotspot.style.width = rightX - leftX + 'px';
39
- hotspot.style.height = bottomY - topY + 'px';
40
- }
41
- break;
42
- default:
43
- break;
44
- }
68
+ if (!graphic || !coords || !shape)
69
+ return;
70
+ this.#keepDeclaredRatio(graphic);
71
+ positionShapes(shape, coords.split(',').map(s => parseFloat(s)), graphic, hotspot);
45
72
  }
73
+ #positionHotspotOnRegister = (e) => {
74
+ this.#positionHotspot(e.target);
75
+ };
46
76
  connectedCallback() {
47
77
  super.connectedCallback();
48
78
  this.addEventListener('qti-register-hotspot', this.#positionHotspotOnRegister);
79
+ // A hotspot that connected before this element was upgraded has already dispatched its
80
+ // register event (the hotspot interaction has the same catch-up).
81
+ this.querySelectorAll(':scope > qti-associable-hotspot').forEach(hotspot => this.#positionHotspot(hotspot));
49
82
  }
50
83
  disconnectedCallback() {
51
84
  super.disconnectedCallback();
@@ -1 +1 @@
1
- {"version":3,"file":"qti-graphic-gap-match-interaction.js","sourceRoot":"","sources":["../src/qti-graphic-gap-match-interaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAE3B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC7B,MAAM,gEAAgE,CAAC;AAExE,iGAAiG;AACjG,OAAO,MAAM,MAAM,4CAA4C,CAAC;AAIhE,MAAM,WAAW,GAAG,oBAAoB,CACtC,WAAW,EACX,2BAA2B,EAC3B,wBAAwB,EACxB,oBAAoB,CACrB,CAAC;AAEF,MAAM,OAAO,6BAA8B,SAAQ,4BAA4B,CAAC,WAAW,EAAE,wBAAwB,CAAC;aACpG,WAAM,GAAmB,MAAM,CAAC;IAEvC,MAAM;QACb,OAAO,IAAI,CAAA;;;sEAGuD,CAAC;IACrE,CAAC;IAEe,sCAAsC;QACpD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0BAA0B,CAAC,CAAoB;QAC7C,MAAM,OAAO,GAAG,CAAC,CAAC,MAA0B,CAAC;QAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7D,uDAAuD;QACvD,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,QAAQ;gBACX,CAAC;oBACC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC;oBAChD,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;oBAC7C,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;oBAC5C,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;gBACjE,CAAC;gBACD,MAAM;YAER,KAAK,MAAM;gBACT,CAAC;oBACC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAAC;oBACpD,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC;oBAClC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;oBAChC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;oBAC5C,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC;gBAC/C,CAAC;gBACD,MAAM;YAER;gBACE,MAAM;QACV,CAAC;IACH,CAAC;IAEQ,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,gBAAgB,CAAC,sBAAsB,EAAE,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACjF,CAAC;IACQ,oBAAoB;QAC3B,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,IAAI,CAAC,mBAAmB,CAAC,sBAAsB,EAAE,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACpF,CAAC","sourcesContent":["import { html } from 'lit';\n\nimport { Interaction } from '@qti-components/base';\nimport {\n DragDropSlottedMixin,\n DragDropSlottedSortableMixin\n} from '@qti-components/interactions-core/mixins/drag-drop-observables';\n\n// import { DragDropInteractionMixin } from '@qti-components/interactions-core/mixins/drag-drop';\nimport styles from './qti-graphic-gap-match-interaction.styles';\n\nimport type { QtiHotspotChoice } from '@qti-components/interactions-core/elements/qti-hotspot-choice';\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\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 #positionHotspotOnRegister(e: CustomEvent<null>): void {\n const hotspot = e.target as QtiHotspotChoice;\n const coords = hotspot.getAttribute('coords');\n const shape = hotspot.getAttribute('shape');\n const coordsNumber = coords.split(',').map(s => parseInt(s));\n\n // positionHotspots(shape, coordsNumber, img, hotspot);\n switch (shape) {\n case 'circle':\n {\n const [centerX, centerY, radius] = coordsNumber;\n hotspot.style.left = centerX - radius + 'px';\n hotspot.style.top = centerY - radius + 'px';\n hotspot.style.width = hotspot.style.height = 2 * radius + 'px';\n }\n break;\n\n case 'rect':\n {\n const [leftX, topY, rightX, bottomY] = coordsNumber;\n hotspot.style.left = leftX + 'px';\n hotspot.style.top = topY + 'px';\n hotspot.style.width = rightX - leftX + 'px';\n hotspot.style.height = bottomY - topY + 'px';\n }\n break;\n\n default:\n break;\n }\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('qti-register-hotspot', this.#positionHotspotOnRegister);\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';\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,2 +1,2 @@
1
- declare const _default: import("lit").CSSResult;
1
+ declare const _default: import("lit").CSSResult[];
2
2
  export default _default;
@@ -1,57 +1,93 @@
1
1
  import { css } from 'lit';
2
- export default css `
3
- :host {
4
- display: flex;
5
- align-items: flex-start;
6
- flex-direction: column;
7
- flex-wrap: wrap;
8
- gap: 0.5rem;
9
- }
2
+ import { boxSizing, validationMessage } from '@qti-components/base';
3
+ export default [
4
+ boxSizing,
5
+ validationMessage,
6
+ css `
7
+ :host {
8
+ display: flex;
9
+ align-items: flex-start;
10
+ flex-direction: column;
11
+ flex-wrap: wrap;
12
+ gap: 0.5rem;
13
+ /* Containing block for the absolutely positioned hotspots over the image. Was set on this
14
+ host from the theme. */
15
+ position: relative;
16
+ }
10
17
 
11
- :host(.qti-choices-top) {
12
- flex-direction: column-reverse;
13
- }
14
- :host(.qti-choices-bottom) {
15
- flex-direction: column;
16
- }
17
- :host(.qti-choices-left) {
18
- flex-direction: row-reverse;
19
- & [name='drags'] {
20
- width: 25%;
21
- }
22
- & [part='image'] {
23
- width: 75%;
24
- }
25
- }
26
- :host(.qti-choices-right) {
27
- flex-direction: row;
28
- & [name='drags'] {
29
- width: 25%;
30
- }
31
- & [part='image'] {
32
- width: 75%;
33
- }
34
- }
35
- [part='image'] {
36
- display: block;
37
- position: relative;
38
- }
39
- /* [part='drops'] , */
18
+ :host(.qti-choices-top) {
19
+ flex-direction: column-reverse;
20
+ }
21
+ :host(.qti-choices-bottom) {
22
+ flex-direction: column;
23
+ }
24
+ :host(.qti-choices-left) {
25
+ flex-direction: row-reverse;
26
+ & [name='drags'] {
27
+ width: 25%;
28
+ }
29
+ & [part='image'] {
30
+ /* A cap, not a width: the slot must stay exactly the graphic's box (see below). */
31
+ max-width: 75%;
32
+ }
33
+ }
34
+ :host(.qti-choices-right) {
35
+ flex-direction: row;
36
+ & [name='drags'] {
37
+ width: 25%;
38
+ }
39
+ & [part='image'] {
40
+ /* A cap, not a width: the slot must stay exactly the graphic's box (see below). */
41
+ max-width: 75%;
42
+ }
43
+ }
44
+ /*
45
+ * The coordinate space the drop areas sit on. Every qti-associable-hotspot is position:absolute
46
+ * and placed with left/top percentages of the graphic (positionShapes), so this slot must be
47
+ * (a) a positioning context and (b) exactly the box of the graphic — the same contract as the
48
+ * hotspot interaction's slot.
49
+ *
50
+ * width: fit-content shrink-wraps the slot to the graphic instead of the full row, so a
51
+ * percentage of the slot is a percentage of the picture.
52
+ * max-width: 100% lets the slot, and with it the graphic, narrow to the container.
53
+ */
54
+ [part='image'] {
55
+ display: block;
56
+ position: relative;
57
+ width: fit-content;
58
+ max-width: 100%;
59
+ }
60
+ /* [part='drops'] , */
40
61
 
41
- [name='drags'] {
42
- display: flex;
43
- align-items: flex-start;
44
- flex-wrap: wrap;
45
- flex: 1;
46
- border: 2px solid transparent;
47
- padding: 0.3rem;
48
- border-radius: 0.3rem;
49
- gap: 0.5rem;
50
- }
51
- ::slotted(img) {
52
- display: inline-block;
53
- user-select: none;
54
- pointer-events: none;
55
- }
56
- `;
62
+ [name='drags'] {
63
+ display: flex;
64
+ align-items: flex-start;
65
+ flex-wrap: wrap;
66
+ flex: 1;
67
+ border: 2px solid transparent;
68
+ padding: 0.3rem;
69
+ border-radius: 0.3rem;
70
+ gap: 0.5rem;
71
+ }
72
+ /*
73
+ * Fluid, like the hotspot interaction's graphic: an item declares the graphic at the size its
74
+ * coords were authored in (often a full-resolution picture, 1600px wide), and it must still fit a
75
+ * column or a phone. The drop areas are percentages of it, so they scale along.
76
+ *
77
+ * max-width: 100% shrinks a graphic wider than its container, never upscales a small one.
78
+ * height: auto keeps the aspect ratio the width/height attributes declare; without it the
79
+ * height attribute pins the height while the width shrinks.
80
+ * display: block no inline descender gap under the picture, which would make the slot
81
+ * taller than the graphic and skew every vertical percentage.
82
+ */
83
+ ::slotted(img),
84
+ ::slotted(object) {
85
+ display: block;
86
+ max-width: 100%;
87
+ height: auto;
88
+ user-select: none;
89
+ pointer-events: none;
90
+ }
91
+ `
92
+ ];
57
93
  //# sourceMappingURL=qti-graphic-gap-match-interaction.styles.js.map
@@ -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,eAAe,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDjB,CAAC","sourcesContent":["import { css } from 'lit';\n\nexport default css`\n :host {\n display: flex;\n align-items: flex-start;\n flex-direction: column;\n flex-wrap: wrap;\n gap: 0.5rem;\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 width: 75%;\n }\n }\n :host(.qti-choices-right) {\n flex-direction: row;\n & [name='drags'] {\n width: 25%;\n }\n & [part='image'] {\n width: 75%;\n }\n }\n [part='image'] {\n display: block;\n position: relative;\n }\n /* [part='drops'] , */\n\n [name='drags'] {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n flex: 1;\n border: 2px solid transparent;\n padding: 0.3rem;\n border-radius: 0.3rem;\n gap: 0.5rem;\n }\n ::slotted(img) {\n display: inline-block;\n user-select: none;\n pointer-events: none;\n }\n`;\n"]}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFF;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 [name='drags'] {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n flex: 1;\n border: 2px solid transparent;\n padding: 0.3rem;\n border-radius: 0.3rem;\n gap: 0.5rem;\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/dist/register.js CHANGED
@@ -1,3 +1,7 @@
1
- import { QtiGraphicGapMatchInteraction } from './qti-graphic-gap-match-interaction';
2
- customElements.define('qti-graphic-gap-match-interaction', QtiGraphicGapMatchInteraction);
1
+ import { elements } from './elements';
2
+ for (const { tag, ctor } of elements) {
3
+ if (!customElements.get(tag)) {
4
+ customElements.define(tag, ctor);
5
+ }
6
+ }
3
7
  //# sourceMappingURL=register.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AACpF,cAAc,CAAC,MAAM,CAAC,mCAAmC,EAAE,6BAA6B,CAAC,CAAC","sourcesContent":["import { QtiGraphicGapMatchInteraction } from './qti-graphic-gap-match-interaction';\ncustomElements.define('qti-graphic-gap-match-interaction', QtiGraphicGapMatchInteraction);\n"]}
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,QAAQ,EAAE,CAAC;IACrC,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 { elements } from './elements';\n\nfor (const { tag, ctor } of elements) {\n if (!customElements.get(tag)) {\n customElements.define(tag, ctor);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,16 +1,16 @@
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",
4
+ "version": "1.1.6-next.0",
5
5
  "author": "",
6
6
  "dependencies": {
7
- "@qti-components/base": "^1.4.3",
8
- "@qti-components/interactions-core": "^1.1.4",
7
+ "@qti-components/base": "^2.4.0",
8
+ "@qti-components/interactions-core": "^2.1.3",
9
9
  "@qti-components/utilities": "^1.3.3"
10
10
  },
11
11
  "devDependencies": {
12
12
  "@lit/context": "^1.1.6",
13
- "lit": "^3.3.2"
13
+ "lit": "^3.3.1"
14
14
  },
15
15
  "keywords": [
16
16
  "interactions",
@@ -45,6 +45,10 @@
45
45
  "./styles": {
46
46
  "types": "./dist/qti-graphic-gap-match-interaction.styles.d.ts",
47
47
  "import": "./dist/qti-graphic-gap-match-interaction.styles.js"
48
+ },
49
+ "./elements": {
50
+ "types": "./dist/elements.d.ts",
51
+ "import": "./dist/elements.js"
48
52
  }
49
53
  },
50
54
  "repository": {
@@ -54,7 +58,9 @@
54
58
  },
55
59
  "scripts": {
56
60
  "attw": "attw --profile esm-only --pack",
57
- "build": "tsc --paths null",
58
- "clean": "rm -rf dist"
61
+ "build": "pnpm run clean && tsc --paths null",
62
+ "clean": "rm -rf dist",
63
+ "yalc": "yalc publish",
64
+ "yalc:push": "yalc push"
59
65
  }
60
66
  }