cedar-embeddable-editor 1.5.2 → 2.0.1
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/CHANGELOG.md +1013 -0
- package/README.md +552 -110
- package/bundle-manifest.json +9 -0
- package/cedar-embeddable-editor.d.ts +282 -0
- package/cedar-embeddable-editor.js +6053 -1
- package/license.txt +26 -0
- package/package.json +17 -11
package/README.md
CHANGED
|
@@ -1,12 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CEDAR Embeddable Editor (CEE)
|
|
2
2
|
|
|
3
|
-
The CEDAR Embeddable Editor is
|
|
3
|
+
The CEDAR Embeddable Editor (CEE) is a reusable Web Component for adding
|
|
4
|
+
structured, standards-based metadata authoring to web applications.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
CEE dynamically renders data-entry forms from machine-actionable CEDAR
|
|
7
|
+
templates and produces semantically rich metadata as JSON-LD. Templates define
|
|
8
|
+
the fields, constraints, controlled vocabularies, and repeatable structures in a
|
|
9
|
+
form, allowing the metadata-authoring experience to evolve independently of the
|
|
10
|
+
application that embeds it. CEE also supports ontology-backed value selection
|
|
11
|
+
and persistent identifiers from external authorities such as ORCID and ROR.
|
|
12
|
+
|
|
13
|
+
For the design rationale, architecture, and deployments in research platforms,
|
|
14
|
+
see [*Author Once, Publish Everywhere: Portable Metadata Authoring with the CEDAR
|
|
15
|
+
Embeddable Editor*](https://doi.org/10.5334/dsj-2026-002), published in the
|
|
16
|
+
*Data Science Journal* (2026).
|
|
17
|
+
|
|
18
|
+
This README covers building, testing and releasing the component. For embedding
|
|
19
|
+
it in an application, the CEDAR documentation site carries a fuller guide:
|
|
20
|
+
[CEDAR Embeddable Editor](https://metadatacenter.readthedocs.io/en/latest/cedar-embeddable-editor/intro/).
|
|
21
|
+
|
|
22
|
+
## Browser support
|
|
23
|
+
|
|
24
|
+
CEE supports the browser targets of the Angular version each release is built
|
|
25
|
+
with. It requires native Custom Elements v1 and native Shadow DOM.
|
|
26
|
+
|
|
27
|
+
Automated compatibility tests run against current desktop Chromium, Firefox and
|
|
28
|
+
WebKit engines. Firefox ESR and the configured Edge, Safari and iOS versions are
|
|
29
|
+
compilation targets, but are not all exercised as separate browser products.
|
|
30
|
+
|
|
31
|
+
Internet Explorer, legacy EdgeHTML, and browsers or embedded web views without
|
|
32
|
+
native `window.customElements` and Shadow DOM support are not supported. CEE
|
|
33
|
+
does not polyfill its host page. Consumers choosing to support browsers outside
|
|
34
|
+
this contract must load and maintain their own Web Components polyfills before
|
|
35
|
+
loading CEE.
|
|
6
36
|
|
|
7
37
|
## Running as a standalone application
|
|
8
38
|
|
|
9
|
-
You can run CEE as a standalone application. This is helpful for developers to
|
|
39
|
+
You can run CEE as a standalone application. This is helpful for developers to
|
|
40
|
+
see changes to the code reflected immediately in the application. The standalone
|
|
41
|
+
app fetches a small template and instance from `src/assets/cee-demo` and assigns
|
|
42
|
+
them to `templateAndInstanceObject`, the same way any host supplies an artifact,
|
|
43
|
+
so it needs no separate template server and no `cedar-component-distribution`
|
|
44
|
+
checkout.
|
|
10
45
|
|
|
11
46
|
Proceed with the following steps:
|
|
12
47
|
|
|
@@ -16,12 +51,11 @@ Clone this repository onto a local directory of your choice:
|
|
|
16
51
|
|
|
17
52
|
```shell
|
|
18
53
|
git clone https://github.com/metadatacenter/cedar-embeddable-editor.git
|
|
19
|
-
git clone https://github.com/metadatacenter/cedar-component-distribution.git
|
|
20
54
|
```
|
|
21
55
|
|
|
22
56
|
### Edit configuration
|
|
23
57
|
|
|
24
|
-
1. Open the file ```app/app.component.dev.ts``` in your favorite editor.
|
|
58
|
+
1. Open the file ```cedar-embeddable-editor/src/app/app.component.dev.ts``` in your favorite editor.
|
|
25
59
|
2. Edit configuration parameters based on your local environment (see section [Configuration](https://github.com/metadatacenter/cedar-embeddable-editor/tree/develop#configuration) for details).
|
|
26
60
|
|
|
27
61
|
### Build the project and start the server
|
|
@@ -36,37 +70,187 @@ cedar-embeddable-editor$ npm install
|
|
|
36
70
|
cedar-embeddable-editor$ ng serve
|
|
37
71
|
```
|
|
38
72
|
|
|
39
|
-
1. In
|
|
73
|
+
1. In your browser, navigate to `http://localhost:4400/`. The app will automatically reload if you change any of the source files.
|
|
74
|
+
|
|
75
|
+
## Building the Web Component
|
|
76
|
+
|
|
77
|
+
CEE is shipped as one JavaScript file that can be embedded in an application or
|
|
78
|
+
HTML page. Do not concatenate named Angular output files manually: their names,
|
|
79
|
+
locations, and module structure change when Angular changes builders.
|
|
80
|
+
|
|
81
|
+
Build the production application, then run the browser suite against the
|
|
82
|
+
single-file bundle it produced:
|
|
83
|
+
|
|
40
84
|
```shell
|
|
41
|
-
|
|
85
|
+
nvm use
|
|
86
|
+
npm run build:production
|
|
87
|
+
npm run test:visual:prebuilt
|
|
42
88
|
```
|
|
43
89
|
|
|
44
|
-
|
|
90
|
+
One Node version throughout — 24.19.0, which `.nvmrc` names. The build and the
|
|
91
|
+
tests used to run on different ones, because Angular 14's toolchain and the
|
|
92
|
+
Playwright the suite needs did not accept the same version; from Angular 15 they
|
|
93
|
+
do, so the dist that ships is produced on the same Node that exercised it.
|
|
94
|
+
|
|
95
|
+
Once that exact bundle is green, stage the publishable npm directory from it:
|
|
96
|
+
|
|
45
97
|
```shell
|
|
46
|
-
|
|
47
|
-
cedar-embeddable-editor$ ng serve
|
|
98
|
+
npm run package:npm:prebuilt
|
|
48
99
|
```
|
|
49
100
|
|
|
50
|
-
|
|
101
|
+
This copies the tested bytes to
|
|
102
|
+
`dist-npm/cedar-embeddable-editor/cedar-embeddable-editor.js`, refreshes its
|
|
103
|
+
version, README, changelog, and package lock, and records the bundle manifest.
|
|
104
|
+
The command fails if the browser bundle is stale or does not match its SHA-256
|
|
105
|
+
digest. `npm run check:npm-package` can repeat the byte-for-byte verification
|
|
106
|
+
before `npm pack` or `npm publish`.
|
|
107
|
+
|
|
108
|
+
## Running as an `npm` package
|
|
109
|
+
|
|
110
|
+
Releases are published to npmjs.org as
|
|
111
|
+
[`cedar-embeddable-editor`](https://www.npmjs.com/package/cedar-embeddable-editor)
|
|
112
|
+
under the `latest` tag, so an embedder installs the current one by name:
|
|
113
|
+
|
|
114
|
+
```shell
|
|
115
|
+
npm install cedar-embeddable-editor
|
|
116
|
+
```
|
|
51
117
|
|
|
52
|
-
|
|
118
|
+
`1.6.0` is current on npmjs.org.
|
|
53
119
|
|
|
54
|
-
|
|
120
|
+
Dev snapshots go somewhere else: the BMIR Nexus, as the scoped
|
|
121
|
+
`@org.metadatacenter/cedar-embeddable-editor` under a `dev` tag. `scripts/npm-package.mjs`
|
|
122
|
+
derives which from the version — a `-dev.` in it selects the scoped name and the Nexus
|
|
123
|
+
registry, and anything else the unscoped name and the default one — so the two channels
|
|
124
|
+
cannot be confused by a flag someone forgets to pass. npm routes by scope rather than by
|
|
125
|
+
package name, which is what lets one package come from Nexus while everything else resolves
|
|
126
|
+
from npmjs.org.
|
|
55
127
|
|
|
56
|
-
|
|
128
|
+
A CEDAR frontend names a snapshot through an npm alias:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
"cedar-embeddable-editor": "npm:@org.metadatacenter/cedar-embeddable-editor@2.0.0-dev.20260816.5e7dca6"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Cutting one is in
|
|
135
|
+
[CEE-RUNBOOK.md](https://github.com/metadatacenter/cedar-development/blob/develop/ops/CEE-RUNBOOK.md),
|
|
136
|
+
including the version convention and which host needs what afterwards. Publishing needs the
|
|
137
|
+
Nexus credential; reads are anonymous.
|
|
138
|
+
|
|
139
|
+
## Testing
|
|
140
|
+
|
|
141
|
+
The complete test gate is available from the repository root:
|
|
57
142
|
|
|
58
|
-
1. Run the build command:
|
|
59
143
|
```shell
|
|
60
|
-
|
|
144
|
+
npm run test:ci
|
|
61
145
|
```
|
|
62
|
-
|
|
146
|
+
|
|
147
|
+
It runs, in order:
|
|
148
|
+
|
|
149
|
+
1. `ng lint` over the sources and the ESLint configuration.
|
|
150
|
+
2. A type check of the application and the domain harness, with `strict` on
|
|
151
|
+
throughout.
|
|
152
|
+
3. The unit tests, in Node under Vitest.
|
|
153
|
+
4. The headless domain harness with V8 coverage, and its per-directory coverage
|
|
154
|
+
floors.
|
|
155
|
+
5. A production build, then the Playwright suite against that bundle, in a
|
|
156
|
+
container: the full Chromium baseline at desktop and narrow viewport sizes,
|
|
157
|
+
plus focused Chromium, Firefox and WebKit compatibility checks. The container
|
|
158
|
+
is what makes a screenshot baseline mean the same thing on a laptop and on CI,
|
|
159
|
+
so the pixel budget is zero — see `visual/run-in-container.sh`.
|
|
160
|
+
6. Staging the npm package from the bundle the suite just exercised, which
|
|
161
|
+
checks the raw and gzip size budgets and verifies every staged byte against
|
|
162
|
+
its source.
|
|
163
|
+
|
|
164
|
+
The domain corpora are checked into `harness/fixtures/`; running the tests does
|
|
165
|
+
not require `cedar-artifact-library` or `cedar-test-artifacts` checkouts.
|
|
166
|
+
|
|
167
|
+
`.github/workflows/test.yml` runs the same gate on every pull request and on
|
|
168
|
+
pushes to `main`, `develop` and the `cee-angular-**` branches. Nothing is
|
|
169
|
+
published from CI: releasing is a separate, manual procedure.
|
|
170
|
+
|
|
171
|
+
### Auditing what ships
|
|
172
|
+
|
|
63
173
|
```shell
|
|
64
|
-
|
|
174
|
+
npm run audit:prod
|
|
65
175
|
```
|
|
66
176
|
|
|
67
|
-
|
|
177
|
+
Only runtime dependencies reach the file an embedder downloads, so this audit is
|
|
178
|
+
the one that describes the shipped artifact, and it is deliberately not part of
|
|
179
|
+
`test:ci` — it can fail on a disclosure rather than on a commit, which would
|
|
180
|
+
break an unrelated pull request its author cannot fix.
|
|
181
|
+
|
|
182
|
+
A root `npm audit` reports advisories against `@angular/cli` and the packages
|
|
183
|
+
reached through it. **Never run `npm audit fix --force` here.** npm's idea of
|
|
184
|
+
fixing that tree is to walk the toolchain years backwards, undoing the Angular
|
|
185
|
+
march to silence warnings about build tooling an embedder never downloads.
|
|
186
|
+
|
|
187
|
+
### First-time setup
|
|
188
|
+
|
|
189
|
+
CEE resolves the model library from `@org.metadatacenter/cedar-model-typescript-library`,
|
|
190
|
+
published to the BMIR Nexus, so no sibling checkout is needed:
|
|
191
|
+
|
|
192
|
+
```shell
|
|
193
|
+
npm ci
|
|
194
|
+
npm --prefix harness ci
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The visual suite installs nothing here. It runs inside Playwright's own container,
|
|
198
|
+
which carries the browsers it drives, and installs its dependencies there against a
|
|
199
|
+
named volume — so it needs Docker running and no `playwright install` of its own.
|
|
200
|
+
|
|
201
|
+
### Node versions during the Angular migration
|
|
202
|
+
|
|
203
|
+
The root `.nvmrc` pins the Node version used to update, lint and compile the
|
|
204
|
+
current Angular version. Move that pin with each completed framework hop.
|
|
205
|
+
|
|
206
|
+
CEE is on **Angular 22.1** and **Node 24.19.0**, named in `.nvmrc`, declared in
|
|
207
|
+
`engines`, and pinned by CI. Angular 22 accepts `^22.22.3 || ^24.15.0 || >=26`; 24
|
|
208
|
+
is the active LTS where 22 is in maintenance, so that is the one CEE uses.
|
|
209
|
+
|
|
210
|
+
Build and test share it. Through Angular 14 they could not: no Node version
|
|
211
|
+
satisfied both that toolchain and the test tools, so CI built on one and switched
|
|
212
|
+
the runner to another without replacing `dist`. `npm run test:ci:prebuilt` is what
|
|
213
|
+
remains of that arrangement — it tests an already-built artifact and deliberately
|
|
214
|
+
does not invoke `ng build`, which is still what CI wants, because it means the
|
|
215
|
+
bytes tested are the bytes that ship.
|
|
216
|
+
|
|
217
|
+
Only that one package comes from Nexus; everything else resolves from npmjs.org.
|
|
218
|
+
An `.npmrc` alongside each of the three `package.json` files maps the
|
|
219
|
+
`@org.metadatacenter` scope to Nexus, and reads need no credentials.
|
|
220
|
+
|
|
221
|
+
Each manifest depends on it under an alias:
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
"cedar-model-typescript-library": "npm:@org.metadatacenter/cedar-model-typescript-library@<version>"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The alias keeps the local import name, so source files import
|
|
228
|
+
`cedar-model-typescript-library` regardless of the published name. To move to a
|
|
229
|
+
newer build, publish it to Nexus and bump the version in the root and `visual/`
|
|
230
|
+
manifests together. The harness declares no separate copy; it resolves the root
|
|
231
|
+
installation.
|
|
232
|
+
|
|
233
|
+
### Focused test commands
|
|
234
|
+
|
|
235
|
+
Use these when working on one layer:
|
|
236
|
+
|
|
237
|
+
```shell
|
|
238
|
+
npm run test:unit:ci # Vitest unit tests, one run
|
|
239
|
+
npm run test:unit:coverage # unit tests with coverage report
|
|
240
|
+
npm run test:domain # Vitest domain harness
|
|
241
|
+
npm run test:domain:coverage # domain harness with coverage report
|
|
242
|
+
npm run test:bundle-size # exact raw and gzip budgets for the shipped bundle
|
|
243
|
+
npm run test:visual # production build, fixture preparation, Playwright
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`npm test` runs the unit tests once, and `npm run test:watch` keeps them running
|
|
247
|
+
for interactive development. Use `npm run test:ci` for a complete verification.
|
|
68
248
|
|
|
69
|
-
|
|
249
|
+
The unit tests run in Node rather than a browser. None of them uses `TestBed`, so
|
|
250
|
+
none needs Angular's JIT compiler to build a component, and dropping the browser
|
|
251
|
+
took the suite from a Chrome launch to about a second. Anything that does need a
|
|
252
|
+
real browser belongs in the Playwright suite under `visual/`, which tests the
|
|
253
|
+
shipped bundle rather than the sources.
|
|
70
254
|
|
|
71
255
|
## Configuration
|
|
72
256
|
|
|
@@ -75,11 +259,11 @@ Please import the latest version of the editor into your project from: [https://
|
|
|
75
259
|
The CEE configuration file format and storage location depends on the application and the mode in which CEE is being used.
|
|
76
260
|
|
|
77
261
|
* When running CEE in the standalone mode (developer mode), the configuration parameters are stored in and read from the file: `src/app/app.component.dev.ts`.
|
|
78
|
-
* When running CEE as a generic Webcomponent, the configuration parameters can be stored in any `.json` file that is visible to the application that embeds CEE Webcomponent.
|
|
262
|
+
* When running CEE as a generic Webcomponent, the configuration parameters can be stored in any `.json` file that is visible to the application that embeds CEE Webcomponent. Fetch it and assign the result:
|
|
79
263
|
```javascript
|
|
80
|
-
|
|
264
|
+
customElements.whenDefined('cedar-embeddable-editor').then(async () => {
|
|
81
265
|
const cee = document.querySelector('cedar-embeddable-editor');
|
|
82
|
-
cee.
|
|
266
|
+
cee.config = await (await fetch('assets/data/cee-config.json')).json();
|
|
83
267
|
});
|
|
84
268
|
```
|
|
85
269
|
* The configuration can also be passed into the editor as a json map. In Angular this looks as follows:
|
|
@@ -94,63 +278,224 @@ document.addEventListener('WebComponentsReady', function () {
|
|
|
94
278
|
|
|
95
279
|
### Required configuration parameters
|
|
96
280
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
281
|
+
Two keys name the CEDAR services CEE calls, and neither has a default. CEE cannot
|
|
282
|
+
know which deployment it is embedded in, and a default would name one — so a key
|
|
283
|
+
left unset turns its lookups off and CEE reports which key is missing, rather than
|
|
284
|
+
sending a host's users' keystrokes to somebody else's server.
|
|
285
|
+
|
|
286
|
+
Both are bases and both must end in a slash. Every path below them is CEE's own.
|
|
287
|
+
|
|
288
|
+
* **terminologyBaseUrl:** the CEDAR terminology server, which searches BioPortal.
|
|
289
|
+
Unset, controlled fields offer no terms.
|
|
290
|
+
* **bridgeBaseUrl:** the CEDAR bridge server, which reaches the external
|
|
291
|
+
authorities. Unset, the seven authority fields offer no terms and resolve no
|
|
292
|
+
identifiers.
|
|
101
293
|
|
|
102
294
|
```json
|
|
103
295
|
{
|
|
104
|
-
"
|
|
105
|
-
"
|
|
296
|
+
"terminologyBaseUrl": "https://terminology.metadatacenter.org/",
|
|
297
|
+
"bridgeBaseUrl": "https://bridge.metadatacenter.org/"
|
|
106
298
|
}
|
|
107
299
|
```
|
|
108
300
|
|
|
109
301
|
### Optional configuration parameters
|
|
110
302
|
|
|
111
|
-
|
|
303
|
+
Every other key is optional. The defaults below are the component's own, read
|
|
304
|
+
from `CedarEmbeddableMetadataEditorComponent` and its wrapper, not from the
|
|
305
|
+
standalone developer app in `src/app/app.component.dev.ts`, whose values differ.
|
|
306
|
+
|
|
307
|
+
What the user sees:
|
|
308
|
+
|
|
309
|
+
| Key | Default |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `showTemplateDescription` | `false` |
|
|
312
|
+
|
|
313
|
+
CEE draws no page chrome of its own. It used to render a header carrying the CEDAR
|
|
314
|
+
logo and title, and a footer carrying the Stanford Division of Computational
|
|
315
|
+
Medicine mark and a contact link, behind `showHeader` and `showFooter`. Every string
|
|
316
|
+
and destination was hardcoded, so an embedder took CEDAR's branding or nothing.
|
|
317
|
+
A host renders its own header and footer around the element; the standalone app in
|
|
318
|
+
`src/app/app.component.dev.html` is a worked example.
|
|
319
|
+
|
|
320
|
+
What CEE keeps is the CEDAR mark and the version stamp inside the form's own title
|
|
321
|
+
block, which is a component naming itself rather than dressing someone else's page.
|
|
322
|
+
|
|
323
|
+
Editing behaviour and serialization:
|
|
324
|
+
|
|
325
|
+
| Key | Default |
|
|
326
|
+
|---|---|
|
|
327
|
+
| `readOnlyMode` | `false` |
|
|
328
|
+
| `trustTemplateRichText` | `false` |
|
|
329
|
+
|
|
330
|
+
`showDownloadMenu` offers a menu that saves CEE's views of the artifact as files.
|
|
331
|
+
It defaults to `false`, and nothing is rendered under the form either way:
|
|
332
|
+
|
|
333
|
+
| Menu entry | Saves | As |
|
|
334
|
+
|---|---|---|
|
|
335
|
+
| JSON-LD - Instance | The instance as a CEDAR document | `<name>-instance.json` |
|
|
336
|
+
| YAML - Instance | The same instance, as CEDAR YAML | `<name>-instance.yaml` |
|
|
337
|
+
| Compact YAML - Instance | The same instance without root identity and provenance metadata | `<name>-instance-compact.yaml` |
|
|
338
|
+
| JSON Schema - Template | The template as the host supplied it | `<name>-template.json` |
|
|
339
|
+
| YAML - Template | The same template, as CEDAR YAML | `<name>-template.yaml` |
|
|
340
|
+
| Compact YAML - Template | Its compact authoring form, without repository-managed metadata | `<name>-template-compact.yaml` |
|
|
341
|
+
| Data Quality Report | Required-field tally and constraint violations | `<name>-data-quality.json` |
|
|
342
|
+
|
|
343
|
+
`<name>` is the template's own `schema:name`, reduced to file-name-safe
|
|
344
|
+
characters, so a developer with several forms open can tell the files apart.
|
|
345
|
+
|
|
346
|
+
These were eight panels once, each printing a dump under the form, and each
|
|
347
|
+
costing two keys — one to show it and one to expand it. Two of the sixteen were
|
|
348
|
+
on by default, so an embedder who configured nothing got a JSON Schema dump and
|
|
349
|
+
a JSON-LD dump beneath every form.
|
|
350
|
+
|
|
351
|
+
A download is started by the page, which a host running under a restrictive
|
|
352
|
+
sandbox can refuse, with no event to observe when it does. CEE traces each
|
|
353
|
+
attempt through the event handler, so a developer seeing the trace and no file
|
|
354
|
+
knows to look at their own sandbox.
|
|
355
|
+
|
|
356
|
+
Language:
|
|
357
|
+
|
|
358
|
+
| Key | Default |
|
|
359
|
+
|---|---|
|
|
360
|
+
| `defaultLanguage` | `en` |
|
|
361
|
+
| `fallbackLanguage` | `en` |
|
|
362
|
+
| `languageMapPathPrefix` | none |
|
|
363
|
+
|
|
364
|
+
`trustTemplateRichText` decides whether a template author's rich text renders verbatim
|
|
365
|
+
or is sanitized first. It defaults to `false` and should stay there unless your
|
|
366
|
+
application controls which templates load — see [Embedding security](#embedding-security).
|
|
367
|
+
|
|
368
|
+
`bridgeBaseUrl` is the whole of the external-authority surface, covering the
|
|
369
|
+
seven authorities: ORCID, ROR, PFAS, PubMed, RRID, NIH Grant and DOI. CEE appends
|
|
370
|
+
the bridge server's `ext-auth/` resource, then the path for the authority a field
|
|
371
|
+
is bound to — a search path for a name typed into it, and a details path for an
|
|
372
|
+
identifier pasted into it. All of that is the bridge server's own route shape, so
|
|
373
|
+
none of it is configurable: a deployment moves all fourteen endpoints by moving
|
|
374
|
+
the base, or none of them.
|
|
375
|
+
|
|
376
|
+
| Authority | Search path | Details path |
|
|
377
|
+
|---|---|---|
|
|
378
|
+
| ORCID | `orcid/search-by-name` | `orcid` |
|
|
379
|
+
| ROR | `ror/search-by-name` | `ror` |
|
|
380
|
+
| PFAS | `comp-tox/search-by-name` | `comp-tox` |
|
|
381
|
+
| PubMed | `pmid/search-by-name` | `pmid` |
|
|
382
|
+
| RRID | `rrid/search-by-name` | `rrid` |
|
|
383
|
+
| NIH Grant | `nih-grant/search-by-name` | `nih-grant` |
|
|
384
|
+
| DOI | `doi/search-by-name` | `doi` |
|
|
385
|
+
|
|
386
|
+
### TypeScript types
|
|
387
|
+
|
|
388
|
+
The package ships declarations. A host importing them gets a checked configuration
|
|
389
|
+
object and a typed element:
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import type { CeeConfig, CedarEmbeddableEditorElement } from 'cedar-embeddable-editor';
|
|
393
|
+
|
|
394
|
+
const config: CeeConfig = { readOnlyMode: true, showDownloadMenu: true };
|
|
395
|
+
|
|
396
|
+
// Typed by the package, with no cast: it declares the tag in HTMLElementTagNameMap.
|
|
397
|
+
const cee = document.querySelector('cedar-embeddable-editor');
|
|
398
|
+
cee!.config = config;
|
|
399
|
+
const report = cee!.dataQualityReport; // CeeDataQualityReport
|
|
400
|
+
```
|
|
112
401
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
402
|
+
The declarations are **types only**. The bundle is a script that registers a custom
|
|
403
|
+
element and exports no values, so there is nothing to import at runtime — use
|
|
404
|
+
`import type`, and let the interface rather than a constant catch a mistyped key.
|
|
405
|
+
|
|
406
|
+
If you are not using TypeScript, or your configuration comes from a JSON file no
|
|
407
|
+
compiler has seen, CEE checks it at runtime instead and reports what it cannot use.
|
|
408
|
+
An unknown key is named, with the nearest real key suggested; a value of the wrong
|
|
409
|
+
kind says what was expected; and settings that conflict are called out. The messages
|
|
410
|
+
go to the console and to any `eventHandler` you registered:
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
CEE ERROR: Unknown configuration key "readOnlyMod". It has no effect. Did you mean "readOnlyMode"?
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
A key CEE cannot use is reported *and* refused: it reads as unset, so the setting
|
|
417
|
+
keeps the default it documents. One bad key costs only that key — every other key in
|
|
418
|
+
the same configuration applies. CEE does not repair a value either, so a base URL
|
|
419
|
+
missing its trailing slash is dropped rather than completed, since appending CEE's
|
|
420
|
+
own path to it would name an endpoint nobody chose.
|
|
421
|
+
|
|
422
|
+
An assignment that is not an object at all configures nothing and does not spend the
|
|
423
|
+
one assignment there is, so your next attempt is still your first.
|
|
424
|
+
|
|
425
|
+
Configuration and the artifact inputs take one assignment each and keep it. Assign
|
|
426
|
+
`config` a second time, or an artifact input a second time, and CEE reports it and
|
|
427
|
+
ignores it: the first value stands. Build the configuration you want, assign it once,
|
|
428
|
+
and create a new element if it has to change. `eventHandler` is the exception and may
|
|
429
|
+
be replaced, with the last handler assigned receiving — register it before the
|
|
430
|
+
configuration and the artifact if you want the diagnostics from those, since a
|
|
431
|
+
handler hears only what follows it.
|
|
432
|
+
|
|
433
|
+
The same handler provides the lifecycle signal a host can use instead of polling
|
|
434
|
+
the DOM. `ready` is called once after the element's first successful form render;
|
|
435
|
+
it is not called for a rejected artifact and is not replayed to a handler attached
|
|
436
|
+
after rendering:
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
cee.eventHandler = {
|
|
440
|
+
error: (message) => console.error(message),
|
|
441
|
+
ready: () => startAutosave(),
|
|
442
|
+
};
|
|
443
|
+
```
|
|
119
444
|
|
|
120
|
-
|
|
121
|
-
|
|
445
|
+
`readOnlyMode` is the only way in or out of read-only mode. CEE used to offer the
|
|
446
|
+
user a switch of its own, in a preferences menu, which wrote to the same state the
|
|
447
|
+
widgets read — so a form you embedded as a viewer could be made editable from inside
|
|
448
|
+
it. Both are gone, along with the `showPreferencesMenu` key that governed the menu.
|
|
122
449
|
|
|
123
|
-
|
|
124
|
-
"expandedInstanceDataCore": false,
|
|
450
|
+
## Embedding security
|
|
125
451
|
|
|
126
|
-
|
|
127
|
-
|
|
452
|
+
CEE renders inside your page, in your origin. It is a custom element using Shadow
|
|
453
|
+
DOM, and **Shadow DOM is not a security boundary**: it scopes styles and markup, not
|
|
454
|
+
privileges. Anything CEE executes runs with the same access to cookies, storage and
|
|
455
|
+
network as the rest of your application.
|
|
128
456
|
|
|
129
|
-
|
|
130
|
-
"expandedInstanceDataFull": false,
|
|
457
|
+
That matters for one input in particular.
|
|
131
458
|
|
|
132
|
-
|
|
133
|
-
"expandedTemplateSourceData": false,
|
|
459
|
+
### Templates are trusted input
|
|
134
460
|
|
|
135
|
-
|
|
136
|
-
|
|
461
|
+
A template can carry a **static rich-text field**, whose body is HTML composed by the
|
|
462
|
+
template's author and rendered as HTML by CEE. Instance data is different and is
|
|
463
|
+
always sanitized — a value a form's user typed can never introduce markup that runs.
|
|
464
|
+
The question is only what a *template author* may do.
|
|
137
465
|
|
|
138
|
-
|
|
139
|
-
|
|
466
|
+
CEE sanitizes template rich text by default. Script elements, event-handler
|
|
467
|
+
attributes such as `onerror`, `javascript:` URLs, `iframe`, `form` controls and
|
|
468
|
+
AngularJS directive attributes such as `ng-click` are removed. Formatting is
|
|
469
|
+
preserved: inline styles, tables, lists, headings, links, and inline `data:` images
|
|
470
|
+
in the raster formats all render as the author composed them.
|
|
140
471
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
472
|
+
If your application decides which templates load — they ship with the application, or
|
|
473
|
+
come from a source you control — you may prefer the author's markup to render exactly
|
|
474
|
+
as written:
|
|
144
475
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"readOnlyMode": false,
|
|
149
|
-
"hideEmptyFields": false,
|
|
150
|
-
"showPreferencesMenu": true
|
|
476
|
+
```json
|
|
477
|
+
{
|
|
478
|
+
"trustTemplateRichText": true
|
|
151
479
|
}
|
|
152
480
|
```
|
|
153
|
-
|
|
481
|
+
|
|
482
|
+
**Only set this if template authors are as trusted as your own application code.**
|
|
483
|
+
With it on, a template author can run JavaScript in your origin. "Allowed to define a
|
|
484
|
+
form" and "allowed to run code in this page" are very different permissions, and this
|
|
485
|
+
key is where you say they are the same for your deployment.
|
|
486
|
+
|
|
487
|
+
In particular, **do not set it if your users choose their own templates** — from
|
|
488
|
+
CEDAR's public library, or from anywhere your users can write to. Leave it off and
|
|
489
|
+
CEE will render the formatting without the risk.
|
|
490
|
+
|
|
491
|
+
### What is sanitized where
|
|
492
|
+
|
|
493
|
+
| Content | Origin | Treatment |
|
|
494
|
+
|---|---|---|
|
|
495
|
+
| Static rich-text field body | Template author | Sanitized, unless `trustTemplateRichText` is on |
|
|
496
|
+
| Static section break, image, YouTube | Template author | Not rendered as HTML; content is used as text or a URL |
|
|
497
|
+
| Field values, in the form and in read-only view | Instance data | Always sanitized. Not configurable |
|
|
498
|
+
|
|
154
499
|
## Metadata API
|
|
155
500
|
|
|
156
501
|
CEE Webcomponent includes APIs for exporting metadata externally and importing metadata into CEE.
|
|
@@ -163,12 +508,28 @@ The metadata currently being edited inside CEE can be exported at anytime by mak
|
|
|
163
508
|
const meta = cee.currentMetadata;
|
|
164
509
|
```
|
|
165
510
|
|
|
511
|
+
`currentMetadata` always returns a CEDAR JSON object. For YAML, read the
|
|
512
|
+
companion accessor instead:
|
|
513
|
+
|
|
514
|
+
```javascript
|
|
515
|
+
const yaml = cee.currentMetadataYaml; // always a YAML string
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Either accessor works whatever form the template arrived in. A template written
|
|
519
|
+
as CEDAR YAML is assigned to `templateObject` like any other, as the parsed YAML
|
|
520
|
+
object rather than the YAML source string, and CEE picks the reader from the
|
|
521
|
+
template's own shape:
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
cee.templateObject = parsedTemplateYaml;
|
|
525
|
+
```
|
|
526
|
+
|
|
166
527
|
In the example below, the metadata is sent to an external endpoint every 15 seconds:
|
|
167
528
|
|
|
168
529
|
```javascript
|
|
169
|
-
|
|
530
|
+
customElements.whenDefined('cedar-embeddable-editor').then(async () => {
|
|
170
531
|
const cee = document.querySelector('cedar-embeddable-editor');
|
|
171
|
-
cee.
|
|
532
|
+
cee.config = await (await fetch('assets/data/cee-config.json')).json();
|
|
172
533
|
const saveTime = 15000; // 15 seconds
|
|
173
534
|
|
|
174
535
|
setInterval(() => {
|
|
@@ -200,38 +561,32 @@ You can inject your metadata into CEE, provided it matches the template currentl
|
|
|
200
561
|
cee.instanceObject = yourCustomMetadataJson
|
|
201
562
|
```
|
|
202
563
|
|
|
203
|
-
|
|
564
|
+
`templateObject` and `instanceObject` are independent, and either may be assigned
|
|
565
|
+
first: CEE does not build the form until a template is present, so an instance
|
|
566
|
+
supplied ahead of one waits rather than loading against nothing.
|
|
204
567
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const xhr = new XMLHttpRequest();
|
|
208
|
-
xhr.onreadystatechange = () => {
|
|
209
|
-
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
210
|
-
if (xhr.status === 200) {
|
|
211
|
-
const jsonMeta = JSON.parse(xhr.responseText);
|
|
212
|
-
cee.instanceObject = jsonMeta;
|
|
213
|
-
|
|
214
|
-
if (successHandler) {
|
|
215
|
-
successHandler(jsonMeta);
|
|
216
|
-
}
|
|
217
|
-
} else {
|
|
218
|
-
if (errorHandler) {
|
|
219
|
-
errorHandler(xhr);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
xhr.open('GET', metaUrl, true);
|
|
225
|
-
xhr.send();
|
|
226
|
-
}
|
|
568
|
+
Each takes one assignment. Fetch the metadata before you assign it, rather than
|
|
569
|
+
assigning a placeholder and correcting it once the fetch lands:
|
|
227
570
|
|
|
228
|
-
|
|
571
|
+
```javascript
|
|
572
|
+
customElements.whenDefined('cedar-embeddable-editor').then(async () => {
|
|
229
573
|
const cee = document.querySelector('cedar-embeddable-editor');
|
|
230
|
-
cee.
|
|
231
|
-
|
|
574
|
+
cee.config = await (await fetch('assets/data/cee-config.json')).json();
|
|
575
|
+
cee.instanceObject = await (await fetch('uploads/metadata-for-restore.json')).json();
|
|
576
|
+
cee.templateObject = yourCustomTemplateJson;
|
|
232
577
|
});
|
|
233
578
|
```
|
|
234
579
|
|
|
580
|
+
An instance counts only after CEE can read it. If deserialization fails, CEE
|
|
581
|
+
reports the rejection to the console and to `eventHandler`, does not render a
|
|
582
|
+
replacement empty form, and leaves the instance assignment available for a
|
|
583
|
+
corrected value. The same rule applies to the instance inside
|
|
584
|
+
`templateAndInstanceObject`; a rejected combined value spends neither artifact
|
|
585
|
+
assignment.
|
|
586
|
+
|
|
587
|
+
To load a different instance, create a new element. Reassigning `instanceObject`
|
|
588
|
+
reports an error and leaves the first instance in place.
|
|
589
|
+
|
|
235
590
|
To reiterate, the metadata being injected **MUST** match the template currently being edited and open in your browser window.
|
|
236
591
|
|
|
237
592
|
### Injecting Template And Metadata Together
|
|
@@ -246,6 +601,36 @@ cee.templateAndInstanceObject = templateAndInstance;
|
|
|
246
601
|
Injecting template and metadata together brings performance benefits as well as allows configuring hiding empty fields.
|
|
247
602
|
Object being injected must strictly have two objects one named 'templateObject' and the other 'instanceObject'.
|
|
248
603
|
|
|
604
|
+
### Temporal Values
|
|
605
|
+
|
|
606
|
+
CEE treats a temporal field's declared `temporalType`, `temporalGranularity`
|
|
607
|
+
and `timezoneEnabled` settings as its storage contract. The editor shows only
|
|
608
|
+
the parts named by that contract and emits a complete lexical `xsd:date`,
|
|
609
|
+
`xsd:time` or `xsd:dateTime` value:
|
|
610
|
+
|
|
611
|
+
| Declared precision | Canonical stored example |
|
|
612
|
+
| --- | --- |
|
|
613
|
+
| date, year | `2026-01-01` |
|
|
614
|
+
| date, month | `2026-08-01` |
|
|
615
|
+
| date, day | `2026-08-09` |
|
|
616
|
+
| time, hour | `21:00:00` |
|
|
617
|
+
| time, minute | `21:45:00` |
|
|
618
|
+
| time, second | `21:45:32` |
|
|
619
|
+
| time, decimal second | `21:45:32.001` |
|
|
620
|
+
| date-time, day | `2026-08-09T00:00:00` |
|
|
621
|
+
| date-time, minute | `2026-08-09T21:45:00` |
|
|
622
|
+
|
|
623
|
+
The same padding rule applies to the other date-time granularities. If time
|
|
624
|
+
zones are enabled, CEE appends the selected fixed offset (`Z` or `+/-HH:mm`);
|
|
625
|
+
if they are disabled, any offset is removed.
|
|
626
|
+
|
|
627
|
+
Granularity is authoritative when an existing instance is loaded. Information
|
|
628
|
+
finer than the declared granularity is intentionally discarded and the
|
|
629
|
+
canonical value is written back. For example,
|
|
630
|
+
`2026-08-09T21:45:32.125-07:00` in a day-granularity date-time field becomes
|
|
631
|
+
`2026-08-09T00:00:00-07:00`. Embedders should account for that normalization
|
|
632
|
+
when comparing a saved instance with its original input.
|
|
633
|
+
|
|
249
634
|
### Data Quality Report
|
|
250
635
|
|
|
251
636
|
The dataQualityReport summarizes basic metrics on the instance data.
|
|
@@ -254,14 +639,49 @@ The dataQualityReport summarizes basic metrics on the instance data.
|
|
|
254
639
|
const report = cee.dataQualityReport;
|
|
255
640
|
```
|
|
256
641
|
|
|
257
|
-
|
|
642
|
+
The report answers two questions: is anything required missing, and is anything
|
|
643
|
+
present invalid.
|
|
258
644
|
|
|
259
645
|
```
|
|
260
646
|
requiredFieldValueCount: int
|
|
261
647
|
nonNullRequiredFieldValueCount: int
|
|
648
|
+
problems: ValidationProblem[]
|
|
262
649
|
isValid: boolean
|
|
263
650
|
```
|
|
264
651
|
|
|
652
|
+
`isValid` is true when nothing required is missing **and** `problems` is empty.
|
|
653
|
+
|
|
654
|
+
Each problem names the field and what is wrong with it:
|
|
655
|
+
|
|
656
|
+
```javascript
|
|
657
|
+
{
|
|
658
|
+
path: ['_author', '_email'], // component path from the template root
|
|
659
|
+
field: '_email',
|
|
660
|
+
inputType: 'email',
|
|
661
|
+
code: 'email', // stable, matchable without parsing the message
|
|
662
|
+
message: 'Not a valid email address.',
|
|
663
|
+
value: 'not-an-email'
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
Constraints checked: `requiredValue`; `minLength`, `maxLength` and `regex`;
|
|
668
|
+
email, link, phone and external-authority IRI format; numeric type — including
|
|
669
|
+
`xsd:decimal`, `xsd:byte` and `xsd:short` — with the type's own range,
|
|
670
|
+
`minValue`, `maxValue` and `decimalPlace`; temporal shape against
|
|
671
|
+
`temporalType`, `granularity` and `timezoneEnabled`, plus calendar validity;
|
|
672
|
+
membership of a value in its declared choice literals; `minItems` and
|
|
673
|
+
`maxItems`; and the structure of a controlled-term value.
|
|
674
|
+
|
|
675
|
+
Controlled-term **membership** — whether a term belongs to the declared
|
|
676
|
+
ontologies, value sets, classes or branches — is not checked. It requires the
|
|
677
|
+
terminology server, and a local synchronous report should not depend on the
|
|
678
|
+
network. Structural checks on controlled values (`@id` and `rdfs:label` present
|
|
679
|
+
as a pair, `@id` well-formed) are performed.
|
|
680
|
+
|
|
681
|
+
An absent value produces no constraint problems; that is the required check's
|
|
682
|
+
business, so an empty form reports what is missing rather than also reporting
|
|
683
|
+
every blank as malformed.
|
|
684
|
+
|
|
265
685
|
### Language Maps / Translations
|
|
266
686
|
|
|
267
687
|
The application currently has two built-in language maps: `en` and `hu`. If you do not specify any language-related config option, the default `English` map will be used.
|
|
@@ -301,27 +721,43 @@ Information about the loading process is logged onto the console with the `CEE T
|
|
|
301
721
|
|
|
302
722
|
### Listening for changes
|
|
303
723
|
|
|
304
|
-
|
|
724
|
+
CEE emits one composed, bubbling `change` event after an operation actually changes
|
|
725
|
+
the serialized instance. It does not forward incidental DOM control traffic: focus,
|
|
726
|
+
blur, paging, read-only controls, and a write that leaves `currentMetadata` unchanged
|
|
727
|
+
produce no event. Field edits, clears, controlled-term selections, and multi-instance
|
|
728
|
+
add, copy, and delete operations do.
|
|
305
729
|
|
|
306
|
-
|
|
730
|
+
The event is a `CustomEvent<CeeChangeDetail>`. Its detail carries the operation,
|
|
731
|
+
template path, supplied value, current validity and full data-quality report, plus
|
|
732
|
+
the current title and description. Multi-instance details also retain their former
|
|
733
|
+
`message` name for compatibility.
|
|
307
734
|
|
|
308
|
-
-
|
|
309
|
-
|
|
310
|
-
<cedar-embeddable-editor
|
|
311
|
-
[config]="conf"
|
|
312
|
-
[templateObject]="template"
|
|
313
|
-
[instanceObject]="instance"
|
|
314
|
-
(change)="logChange($event)"
|
|
315
|
-
></cedar-embeddable-editor>
|
|
316
|
-
```
|
|
735
|
+
The package's custom-element declaration types the listener and its detail without
|
|
736
|
+
a cast:
|
|
317
737
|
|
|
318
|
-
- `component.ts`:
|
|
319
738
|
```typescript
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
739
|
+
import type { CeeChangeDetail } from 'cedar-embeddable-editor';
|
|
740
|
+
|
|
741
|
+
const cee = document.querySelector('cedar-embeddable-editor');
|
|
742
|
+
if (!cee) throw new Error('CEE element is missing');
|
|
743
|
+
|
|
744
|
+
cee.addEventListener('change', (event) => {
|
|
745
|
+
const detail: CeeChangeDetail = event.detail;
|
|
746
|
+
console.log(detail.operation, detail.path);
|
|
747
|
+
console.log('valid:', detail.validity);
|
|
748
|
+
});
|
|
323
749
|
```
|
|
324
750
|
|
|
751
|
+
Framework event bindings receive the same custom event. For example, Angular can
|
|
752
|
+
bind `(change)="logChange($event)"` on the element and type the handler parameter as
|
|
753
|
+
`CustomEvent<CeeChangeDetail>`.
|
|
754
|
+
|
|
755
|
+
The optional `eventHandler.valueChanged(path, value)` callback receives the same
|
|
756
|
+
field mutations. It is not a dirty flag: only the host knows which serialization
|
|
757
|
+
was last loaded or saved. Keep that baseline in the host and compare
|
|
758
|
+
`cee.currentMetadata` after each `change`; doing so also clears dirty state when an
|
|
759
|
+
edit is undone.
|
|
760
|
+
|
|
325
761
|
### Viewer Mode
|
|
326
762
|
|
|
327
763
|
CEE can be used as a viewer to display metadata instances. This can be achieved by the following configuration setting:
|
|
@@ -332,11 +768,17 @@ CEE can be used as a viewer to display metadata instances. This can be achieved
|
|
|
332
768
|
When used in this mode, users won't be able to manipulate the metadata instance but can only read it.
|
|
333
769
|
## Example Applications
|
|
334
770
|
|
|
335
|
-
|
|
336
|
-
|
|
771
|
+
[`cedar-component-demo`](https://github.com/metadatacenter/cedar-component-demo)
|
|
772
|
+
holds small runnable applications that embed CEE, each with its own README:
|
|
337
773
|
|
|
338
|
-
|
|
774
|
+
| Application | Framework |
|
|
775
|
+
|---|---|
|
|
776
|
+
| `cedar-cee-demo-angular-src` | Angular |
|
|
777
|
+
| `cedar-cee-demo-react` | React |
|
|
778
|
+
| `cedar-cee-demo-ember-src` | Ember |
|
|
339
779
|
|
|
340
|
-
|
|
780
|
+
Each edits the same template, `eDNA ECT Demonstration`, kept as a file inside the
|
|
781
|
+
application rather than fetched from a server.
|
|
341
782
|
|
|
342
|
-
|
|
783
|
+
`cedar-cee-demo-angular-src` needs `npm install --legacy-peer-deps`; the others
|
|
784
|
+
do not.
|