@pie-players/pie-tool-picture-dictionary 0.3.67

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/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @pie-players/pie-tool-picture-dictionary
2
+
3
+ Picture dictionary panel for the PIE assessment player. Registers
4
+ `<pie-tool-picture-dictionary>`.
5
+
6
+ ## Lookup is host-supplied
7
+
8
+ PIE ships no endpoint. The symbol corpus behind a picture dictionary is licensed, so
9
+ a host supplies one.
10
+
11
+ ```html
12
+ <pie-tool-picture-dictionary endpoint="/api/picture-dictionary" language="en">
13
+ </pie-tool-picture-dictionary>
14
+ ```
15
+
16
+ ```js
17
+ element.lookup = async ({ keyword, language, max }, signal) => ({
18
+ status: "ok",
19
+ items: [{ url: "/symbols/apple.png", caption: "An apple" }],
20
+ });
21
+ ```
22
+
23
+ With neither, the panel says no service is configured.
24
+
25
+ The endpoint is called `same-origin`, so a route already behind the assessment's own
26
+ session answers with no further configuration. A host authorising some other way
27
+ passes a `headers` function, read per request so a short-lived token is fetched fresh;
28
+ one that wants no ambient credentials passes `credentials: "omit"`. Both are optional
29
+ properties.
30
+
31
+ ### Request
32
+
33
+ `POST` with `{ keyword, language?, max? }` — the shape a picture-dictionary service
34
+ is expected to accept. `keyword` is normalised before it is sent, and a selection
35
+ longer than four words is refused without a request.
36
+
37
+ ### Response
38
+
39
+ ```json
40
+ {
41
+ "pictures": [
42
+ { "url": "/symbols/apple.png", "caption": "An apple", "width": 120, "height": 90 }
43
+ ]
44
+ }
45
+ ```
46
+
47
+ `images` is accepted as an alias for `pictures`, and within an entry `image` as an
48
+ alias for `url` — `url` wins if a payload carries both. Unknown extra fields are
49
+ ignored. Signed, short-lived URLs are expected and fine.
50
+
51
+ ### Pointing it at an existing service
52
+
53
+ A service that already takes `{ keyword, language?, max? }` and answers
54
+ `{ images: [{ image }] }` needs no resolver: name it in `endpoint` and the built-in
55
+ lookup speaks it as it stands.
56
+
57
+ Two things such a service decides rather than this panel. If it is cross-origin from the
58
+ assessment, the `same-origin` default sends no credentials, so a service token goes
59
+ through `headers` — a path a host can use directly instead of proxying. And if it
60
+ defaults the language server-side, a non-default language has to be declared: the panel
61
+ sends `language` only when the host sets it.
62
+
63
+ A picture whose URL is not `https:`, protocol-relative, or a same-origin path is
64
+ dropped — host data still reaches an attribute the browser acts on, and a symbol
65
+ service has no reason to return `javascript:` or `data:`. Plain `http:` is dropped
66
+ because it is mixed content on every https deployment, which is a broken image where
67
+ the definition should be. A leading `/` is checked by resolving rather than by prefix:
68
+ `/\evil.example/x.png` looks like a path and resolves to another host, because a
69
+ backslash is a path separator for special schemes. If every picture is dropped the
70
+ panel reports "no picture" rather than rendering a broken grid.
71
+
72
+ Zero pictures is reported as "no picture", distinct from a service failure.
73
+
74
+ ## Alt text
75
+
76
+ The picture *is* the definition, so it is never decorative and never gets an empty
77
+ `alt`. The caption becomes the `alt`; without one the keyword stands in, which at
78
+ least tells a screen reader user what the picture is meant to depict. The visible
79
+ caption is `aria-hidden`, since it is already the `alt`.
80
+
81
+ ## Two entry points, deliberately
82
+
83
+ The `term` property is set by whatever selection affordance the host offers. The
84
+ field is the tool's keyboard route, not a convenience: a sighted keyboard-only
85
+ learner cannot originate a text selection in non-editable content, because Chromium
86
+ does not extend one with Shift+Arrow there without caret browsing — an OS-level
87
+ toggle that does not exist on mobile. A selection-only picture dictionary would be
88
+ unreachable for exactly the learners most likely to need it.
89
+
90
+ ## Properties
91
+
92
+ | Name | Attribute | Type | Notes |
93
+ | ---------- | ---------- | -------- | ---------------------------------------------- |
94
+ | `visible` | `visible` | boolean | Owned by the toolbar shell. |
95
+ | `toolId` | `tool-id` | string | Scoped tool instance id. |
96
+ | `term` | `term` | string | Pre-fills and searches when the panel is open. |
97
+ | `termRequestId` | — | string \| number | Identity of the current `term`; optional. |
98
+ | `endpoint` | `endpoint` | string | Enables the built-in POST lookup. |
99
+ | `language` | `language` | string | BCP-47 tag sent with the request. |
100
+ | `lookup` | — | function | Host resolver; takes precedence over `endpoint`. |
101
+ | `headers` | — | function | Extra request headers for `endpoint`, read per request. |
102
+ | `credentials` | — | string | Overrides the `same-origin` default for `endpoint`. |
103
+
104
+ A `lookup` resolves to `{ status: "ok", items }`, `{ status: "empty" }`, or
105
+ `{ status: "error", reason }`. `empty` and `error` are separate on purpose: a learner
106
+ must not be told their word is not real when the network is down.
107
+
108
+ The panel renders its body only; floating chrome belongs to the toolbar shell.
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,52 @@
1
+ import { TermLookup, TermLookupRequest, TermLookupResult } from '@pie-players/pie-players-shared/tools/term-lookup';
2
+ export type PictureLookupRequest = TermLookupRequest;
3
+ export interface PictureResult {
4
+ /** `https:`, protocol-relative or same-origin URL. Signed, short-lived URLs are fine. */
5
+ url: string;
6
+ /**
7
+ * What the picture shows.
8
+ *
9
+ * Becomes the image's `alt`. For this tool the picture *is* the definition, so it
10
+ * is never decorative and never gets an empty `alt`; without a caption the keyword
11
+ * stands in.
12
+ */
13
+ caption?: string;
14
+ width?: number;
15
+ height?: number;
16
+ }
17
+ export type PictureLookupResult = TermLookupResult<PictureResult>;
18
+ export type PictureLookup = TermLookup<PictureResult>;
19
+ /** Pictures a single lookup may render. A symbol set rarely has more than a few. */
20
+ export declare const DEFAULT_MAX_PICTURES = 4;
21
+ /**
22
+ * Whether a URL is safe to put in `src`.
23
+ *
24
+ * `https:`, protocol-relative and same-origin paths pass; everything else is refused.
25
+ * `javascript:` and `data:` because a response reaches an attribute the browser
26
+ * executes for some schemes and a symbol service has no reason to return either, and
27
+ * plain `http:` because it is mixed content on every https deployment — the browser
28
+ * blocks it and the learner gets a broken image where the definition should be, which
29
+ * is the outcome this guard exists to prevent.
30
+ *
31
+ * A leading `/` is checked by resolving rather than by prefix: `/\evil.example/x.png`
32
+ * looks like a path and resolves to `https://evil.example/x.png`, because a backslash
33
+ * is a path separator for special schemes and a tab is stripped outright. Both land on
34
+ * https, so neither defeats the mixed-content guard — but "same-origin" is what this
35
+ * function says it means, so it is what it checks.
36
+ */
37
+ export declare function isRenderablePictureUrl(value: string): boolean;
38
+ /** Read a host response into a result, ignoring unknown extra fields. */
39
+ export declare function readPictureResponse(payload: unknown): PictureLookupResult;
40
+ /**
41
+ * A lookup that POSTs to a host endpoint.
42
+ *
43
+ * The session cookie rides along by default, because a host is expected to put its
44
+ * picture route behind the same session boundary as the assessment; `credentials` and
45
+ * `headers` are there for a host that authorises some other way.
46
+ */
47
+ export declare function createEndpointLookup(args: {
48
+ endpoint: string;
49
+ headers?: () => Promise<Record<string, string>> | Record<string, string>;
50
+ credentials?: RequestCredentials;
51
+ fetchImpl?: typeof fetch;
52
+ }): PictureLookup;