@getv.design/embed 0.0.0-stage → 0.1.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.
- package/LICENSE +173 -0
- package/README.md +422 -2
- package/THIRD_PARTY_NOTICES.txt +850 -0
- package/dist/chunks/chunk-622PUN2F.js +2 -0
- package/dist/chunks/chunk-EPAA36UG.js +2 -0
- package/dist/chunks/chunk-ZCGIXCW3.js +8 -0
- package/dist/chunks/chunk-ZF2CPBI7.js +2 -0
- package/dist/chunks/fonts.js +4 -0
- package/dist/edit.d.ts +5 -0
- package/dist/edit.js +2381 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +2 -0
- package/dist/static.d.ts +7 -0
- package/dist/static.js +2 -0
- package/dist/types.d.ts +223 -0
- package/dist/version.d.ts +8 -0
- package/dist/view.d.ts +5 -0
- package/dist/view.js +100 -0
- package/edit.css +2185 -0
- package/fonts/Inter-Black.woff2 +0 -0
- package/fonts/Inter-BlackItalic.woff2 +0 -0
- package/fonts/Inter-Bold.woff2 +0 -0
- package/fonts/Inter-BoldItalic.woff2 +0 -0
- package/fonts/Inter-ExtraBold.woff2 +0 -0
- package/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
- package/fonts/Inter-ExtraLight.woff2 +0 -0
- package/fonts/Inter-ExtraLightItalic.woff2 +0 -0
- package/fonts/Inter-Italic.woff2 +0 -0
- package/fonts/Inter-Light.woff2 +0 -0
- package/fonts/Inter-LightItalic.woff2 +0 -0
- package/fonts/Inter-Medium.woff2 +0 -0
- package/fonts/Inter-MediumItalic.woff2 +0 -0
- package/fonts/Inter-Regular.woff2 +0 -0
- package/fonts/Inter-SemiBold.woff2 +0 -0
- package/fonts/Inter-SemiBoldItalic.woff2 +0 -0
- package/fonts/Inter-Thin.woff2 +0 -0
- package/fonts/Inter-ThinItalic.woff2 +0 -0
- package/fonts/OFL.txt +92 -0
- package/package.json +51 -4
- package/view.css +98 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
The vDesign Embed Licence, version 1.0
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Sagar Saini. All rights not granted below are reserved.
|
|
4
|
+
|
|
5
|
+
This licence lets you use the vDesign Embed free of charge, including in
|
|
6
|
+
commercial products, and pass on copies of it as long as they are unchanged.
|
|
7
|
+
By using, copying or redistributing the Software, You accept this licence.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
1. Definitions
|
|
11
|
+
|
|
12
|
+
"Software" means the vDesign Embed: the JavaScript files, TypeScript
|
|
13
|
+
declaration files and stylesheets in this package, in the form the Licensor
|
|
14
|
+
publishes them, and any copy of them. It does not include the third-party
|
|
15
|
+
components described in clause 6.
|
|
16
|
+
|
|
17
|
+
"Licensor" means Sagar Saini, an individual. You can contact the Licensor at
|
|
18
|
+
sagar@getv.design.
|
|
19
|
+
|
|
20
|
+
"You" means the person or organisation that uses, copies or redistributes the
|
|
21
|
+
Software under this licence.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
2. Grant
|
|
25
|
+
|
|
26
|
+
The Licensor grants You a free, worldwide, non-exclusive, non-transferable,
|
|
27
|
+
royalty-free licence to:
|
|
28
|
+
|
|
29
|
+
(a) use the Software for any purpose, including commercial purposes;
|
|
30
|
+
(b) copy it; and
|
|
31
|
+
(c) redistribute it unmodified, including as part of a website or an
|
|
32
|
+
application that You make, sell or give away.
|
|
33
|
+
|
|
34
|
+
The people who use Your website or application may use the Software as part
|
|
35
|
+
of it. They need nothing further from the Licensor to do so.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
3. Redistribution
|
|
39
|
+
|
|
40
|
+
You may redistribute the Software bundled into Your own product, or serve it,
|
|
41
|
+
as this package and under its own name, from npm or a public npm mirror such
|
|
42
|
+
as a content delivery network, as long as:
|
|
43
|
+
|
|
44
|
+
(a) the copyright and licence notices in the Software, including the comment
|
|
45
|
+
at the start of each JavaScript file, are kept, in the code or in a
|
|
46
|
+
notices file that Your build tool produces from it; and
|
|
47
|
+
(b) when You redistribute the package as a whole, this licence and the file
|
|
48
|
+
THIRD_PARTY_NOTICES.txt go with it.
|
|
49
|
+
|
|
50
|
+
Processing the Software with a bundler, minifier, transpiler or similar build
|
|
51
|
+
tool, including tree-shaking, code-splitting and changing the module format,
|
|
52
|
+
is not modification under this licence, as long as it does not change what the
|
|
53
|
+
Software does. Configuring the Software through its documented options,
|
|
54
|
+
styling it through its documented CSS custom properties, and calling its
|
|
55
|
+
public interface are uses of it, not modifications.
|
|
56
|
+
|
|
57
|
+
Everyone who receives a copy of the Software from You receives this licence
|
|
58
|
+
from the Licensor directly.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
4. Restrictions
|
|
62
|
+
|
|
63
|
+
Except as clauses 2 and 3 allow, You may not:
|
|
64
|
+
|
|
65
|
+
(a) modify the Software, or distribute a modified copy of it or a work based
|
|
66
|
+
on it (a product that includes the unmodified Software is not a work
|
|
67
|
+
based on it);
|
|
68
|
+
(b) decompile, disassemble or otherwise reverse engineer the Software;
|
|
69
|
+
(c) remove or change any copyright, licence or attribution notice in the
|
|
70
|
+
Software; or
|
|
71
|
+
(d) misrepresent where the Software came from, including by presenting it as
|
|
72
|
+
Your own work.
|
|
73
|
+
|
|
74
|
+
Restrictions (a) and (b) apply only to the extent the law allows. Where
|
|
75
|
+
applicable law expressly gives You a right to do something despite a
|
|
76
|
+
restriction like these, this licence does not take that right away. Examples
|
|
77
|
+
are Articles 5 and 6 of EU Directive 2009/24/EC (on the legal protection of
|
|
78
|
+
computer programs) and section 52(1)(ab) and (ac) of the Indian Copyright Act,
|
|
79
|
+
1957, which allow certain acts needed to study how a program works or to make
|
|
80
|
+
an independently created program work with it.
|
|
81
|
+
|
|
82
|
+
Nothing in this licence stops You from making a product that competes with
|
|
83
|
+
vDesign, including one that reads or writes the .vd format.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
5. Your documents and the open format
|
|
87
|
+
|
|
88
|
+
The Licensor claims no rights in the documents You open, render, present or
|
|
89
|
+
edit with the Software, or in what the Software produces from them.
|
|
90
|
+
|
|
91
|
+
The .vd format specification is published separately under the Creative
|
|
92
|
+
Commons Attribution 4.0 International licence (CC BY 4.0). Anyone may
|
|
93
|
+
implement the format, free of charge and without asking permission. This
|
|
94
|
+
licence covers the Software only. See https://getv.design/docs/licence/.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
6. Third-party components
|
|
98
|
+
|
|
99
|
+
This package also contains code and fonts made by others. They are not part
|
|
100
|
+
of the Software, and are listed in THIRD_PARTY_NOTICES.txt with their licences
|
|
101
|
+
in full. Those components are governed by their own licences, not by this one,
|
|
102
|
+
and nothing in this licence limits the rights those licences give You.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
7. Trademarks
|
|
106
|
+
|
|
107
|
+
This licence gives You no rights to the name "vDesign" or to any vDesign logo,
|
|
108
|
+
except to say accurately that Your product uses the Software.
|
|
109
|
+
|
|
110
|
+
You need not show any credit for the Software to the people who use Your
|
|
111
|
+
product: the notices in the code, under clause 3, are the only ones You must
|
|
112
|
+
keep. A visible "Made with vDesign" link is appreciated. The Software shows
|
|
113
|
+
no watermark and needs no licence key.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
8. Network and data
|
|
117
|
+
|
|
118
|
+
The Software sends no telemetry and collects no information about You or the
|
|
119
|
+
people who use Your product. It makes network requests only for:
|
|
120
|
+
|
|
121
|
+
(a) font files, from the configured font location, which is the jsDelivr
|
|
122
|
+
content delivery network (cdn.jsdelivr.net) unless You set another;
|
|
123
|
+
(b) addresses that You, or the documents You give the Software, supply; and
|
|
124
|
+
(c) the Software's own files, from wherever You serve them.
|
|
125
|
+
|
|
126
|
+
These requests are made by the browser or other runtime that runs the
|
|
127
|
+
Software. The services they reach have their own terms and privacy practices.
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
9. Versions and termination
|
|
131
|
+
|
|
132
|
+
The Licensor may publish later versions of the Software under different terms.
|
|
133
|
+
That does not change the licence of a version already published: the licence a
|
|
134
|
+
version was published with is not revoked, except under this clause.
|
|
135
|
+
|
|
136
|
+
If You breach this licence and do not cure the breach within 30 days after the
|
|
137
|
+
Licensor notifies You of it, Your rights under this licence end, and You must
|
|
138
|
+
stop using and redistributing the Software. That does not end the licence of
|
|
139
|
+
anyone who received the Software from You and complies with this licence.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
10. No warranty; limitation of liability
|
|
143
|
+
|
|
144
|
+
The Software is provided free of charge, "as is" and "as available", without
|
|
145
|
+
warranty of any kind, express or implied, including any warranty of
|
|
146
|
+
merchantability, fitness for a particular purpose, title or non-infringement,
|
|
147
|
+
to the fullest extent the law allows. You use it at Your own risk.
|
|
148
|
+
|
|
149
|
+
To the fullest extent the law allows, the Licensor is not liable for any
|
|
150
|
+
indirect, incidental, special, consequential, exemplary or punitive damages,
|
|
151
|
+
or for any loss of profits, revenue, data, business or goodwill, arising out of
|
|
152
|
+
or in connection with the Software or this licence, however caused and on any
|
|
153
|
+
theory of liability, even if the Licensor was told such loss was possible.
|
|
154
|
+
|
|
155
|
+
To the fullest extent the law allows, the Licensor's total liability arising
|
|
156
|
+
out of or in connection with the Software or this licence is limited to the
|
|
157
|
+
greater of the amount You paid the Licensor for the Software and 100 US
|
|
158
|
+
dollars (USD 100).
|
|
159
|
+
|
|
160
|
+
Nothing in this licence excludes or limits any liability that cannot be
|
|
161
|
+
excluded or limited by law, such as liability for fraud, or for death or
|
|
162
|
+
personal injury caused by negligence.
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
11. Governing law and courts
|
|
166
|
+
|
|
167
|
+
This licence, and any dispute arising out of or in connection with it or the
|
|
168
|
+
Software, are governed by the laws of India. The courts in India that have
|
|
169
|
+
jurisdiction over the dispute have exclusive jurisdiction to settle it.
|
|
170
|
+
|
|
171
|
+
This clause does not take away any protection You have, as a consumer, under
|
|
172
|
+
the mandatory law of the country where You live, or any right You have under
|
|
173
|
+
that law to bring proceedings in its courts.
|
package/README.md
CHANGED
|
@@ -1,3 +1,423 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @getv.design/embed
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Render, present and edit vDesign (`.vd`) documents in any web page.
|
|
4
|
+
|
|
5
|
+
Free to use, including commercially: no licence key, no watermark, and
|
|
6
|
+
attribution is appreciated but not required. The code is proprietary, under
|
|
7
|
+
the vDesign Embed Licence in `LICENSE`; the `.vd` format it reads is open, and
|
|
8
|
+
anyone may implement it (see "Licence" below).
|
|
9
|
+
|
|
10
|
+
**Not published yet.** This package is being built in steps; until the first
|
|
11
|
+
release, nothing here is on npm, and the API may still change. While it is
|
|
12
|
+
0.x, a minor version may change the API.
|
|
13
|
+
|
|
14
|
+
The API reference, every option and event, is at
|
|
15
|
+
https://getv.design/docs/embed/api/.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
From npm, with a bundler:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @getv.design/embed
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { view } from '@getv.design/embed/view';
|
|
27
|
+
|
|
28
|
+
const text = '…'; // a .vd document, as text; or pass a URL or a Blob
|
|
29
|
+
const handle = await view(document.querySelector('#design'), text);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Without a bundler, import the ES modules from a CDN. jsDelivr serves the
|
|
33
|
+
package as published; import the entry file itself (not jsDelivr's `+esm`
|
|
34
|
+
rebundling), so the editor and the font code still load only when needed:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<div id="design"></div>
|
|
38
|
+
<script type="module">
|
|
39
|
+
import { view } from 'https://cdn.jsdelivr.net/npm/@getv.design/embed@0.1.0/dist/view.js';
|
|
40
|
+
|
|
41
|
+
const text = await (await fetch('/designs/home.vd')).text();
|
|
42
|
+
await view(document.querySelector('#design'), text);
|
|
43
|
+
</script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Give the element a size in your stylesheet: the view fills it. Under a
|
|
47
|
+
content security policy that refuses inline scripts, put the module in a file
|
|
48
|
+
of your own and link the stylesheets as "Styles and content security policy"
|
|
49
|
+
below describes.
|
|
50
|
+
|
|
51
|
+
The entries:
|
|
52
|
+
|
|
53
|
+
| Import | What it does |
|
|
54
|
+
| ---------------------------------- | ------------------------------------------------------------------ |
|
|
55
|
+
| `@getv.design/embed/static` | A document in, an SVG string out. No DOM: Node 22.3+ and browsers. |
|
|
56
|
+
| `@getv.design/embed/view` | Show a document in an element, page through it, present it. |
|
|
57
|
+
| `@getv.design/embed/edit` | The vDesign editor, with its floating toolbar. |
|
|
58
|
+
| `@getv.design/embed` | `mount(el, source, { mode })`: loads one of the above on demand. |
|
|
59
|
+
| `@getv.design/embed/view.css`, `@getv.design/embed/edit.css` | The styles, for a strict content security policy. |
|
|
60
|
+
| `@getv.design/embed/fonts/*` | The Inter faces, to serve yourself. |
|
|
61
|
+
|
|
62
|
+
## Static SVG
|
|
63
|
+
|
|
64
|
+
`@getv.design/embed/static`: a document in, an SVG string out, with no DOM. Runs in
|
|
65
|
+
Node 22.3+ and in browsers. 22.3 is when `process.getBuiltinModule`
|
|
66
|
+
arrived, which is how exact fonts are read from disk without a Node import.
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { parse, renderSvg } from '@getv.design/embed/static';
|
|
70
|
+
|
|
71
|
+
const { document, problems } = parse(text); // never throws; problems lists what is wrong
|
|
72
|
+
const svg = await renderSvg(document); // or renderSvg(text), a URL, or a Blob
|
|
73
|
+
const second = await renderSvg(document, { page: 1 }); // one page, by 0-based index or id
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
By default text is measured approximately, without font files, so line breaks
|
|
77
|
+
can differ slightly from the app's. `fonts: 'exact'` lays text out from the
|
|
78
|
+
font files the app exports with, so lines break where the app's SVG export
|
|
79
|
+
breaks them:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const svg = await renderSvg(text, {
|
|
83
|
+
fonts: 'exact',
|
|
84
|
+
onProblems: (problems) => console.log(problems),
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
In Node the faces are read from the package's own `fonts/` folder; in a
|
|
89
|
+
browser they are fetched from `fontBase` (see "Fonts" below). The disk is used
|
|
90
|
+
only where Node can actually read it: a browser bundle whose bundler kept a
|
|
91
|
+
`file:` URL from the build machine (webpack 5 does) fetches from `fontBase`
|
|
92
|
+
like any other page. The SVG names
|
|
93
|
+
the family, `Inter`, and carries no font files of its own. If a face cannot be
|
|
94
|
+
read, the SVG is drawn approximately and `onProblems` is given a
|
|
95
|
+
`fonts-unavailable` warning; `renderSvg` does not reject for it.
|
|
96
|
+
|
|
97
|
+
## View and present
|
|
98
|
+
|
|
99
|
+
`@getv.design/embed/view`: show a document in an element, page through it, and present it.
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
import { view } from '@getv.design/embed/view';
|
|
103
|
+
|
|
104
|
+
const handle = await view(el, text, { fit: 'page', page: 0 }); // or a URL, a Blob, a parsed document
|
|
105
|
+
handle.on('problems', (problems) => console.log(problems)); // called at once with the current list
|
|
106
|
+
handle.goTo('pricing'); // a page by id or 0-based index
|
|
107
|
+
await handle.load(newText); // redraws in place; unchanged parts are not rebuilt
|
|
108
|
+
const play = await handle.play(); // present over the view; Escape comes back
|
|
109
|
+
handle.destroy(); // el is left empty, and image URLs are released
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The view adds one `<div>` inside `el` and fills it, so give `el` a size. A
|
|
113
|
+
plain wheel over it scrolls your page as usual; Ctrl or ⌘ with the wheel, or a
|
|
114
|
+
trackpad pinch, zooms, and middle-drag or Space-drag pans. `interactive: false`
|
|
115
|
+
turns all of that off. A page
|
|
116
|
+
is a top-level frame that is not hidden, counted the same way by `renderSvg`.
|
|
117
|
+
Images are drawn from `blob:` URLs, so a page with a content security policy
|
|
118
|
+
needs `img-src blob:`.
|
|
119
|
+
|
|
120
|
+
`mount(el, source, { mode: 'view' })` from `@getv.design/embed` does the same and loads the
|
|
121
|
+
view code only when called; `mode: 'play'` opens straight into the
|
|
122
|
+
presentation, and that handle's `destroy()` removes the view under it too.
|
|
123
|
+
|
|
124
|
+
The view inserts one `<style>` into the page's head; "Styles and content
|
|
125
|
+
security policy" below has the alternative for a page that refuses inline
|
|
126
|
+
styles.
|
|
127
|
+
|
|
128
|
+
## Edit
|
|
129
|
+
|
|
130
|
+
`@getv.design/embed/edit`: the vDesign editor in your page, with its floating toolbar.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
import { edit } from '@getv.design/embed/edit';
|
|
134
|
+
|
|
135
|
+
const editor = await edit(el, text, { fit: 'page' }); // the same sources and options as view()
|
|
136
|
+
editor.on('change', (event) => save(event.text())); // once per undo step; text() is the whole .vd file
|
|
137
|
+
editor.getText(); // the same, at any time: the design, then any images
|
|
138
|
+
editor.undo();
|
|
139
|
+
editor.redo();
|
|
140
|
+
const handle = await editor.view(); // back to a view, where the person was looking
|
|
141
|
+
editor.destroy(); // el is left empty
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
From a view, `await handle.edit()` swaps the view for an editor in the same
|
|
145
|
+
element and keeps the camera; `mount(el, source, { mode: 'edit' })` opens
|
|
146
|
+
straight into one. The editor's code (about 85 KB gzip) loads only then:
|
|
147
|
+
nothing that views a document downloads it.
|
|
148
|
+
|
|
149
|
+
`change` fires once for each step undo would take back: a whole drag is one,
|
|
150
|
+
when the pointer comes up, and so are an undo and a redo. `text()` composes the
|
|
151
|
+
file only when you call it. A picture pasted or dropped onto the canvas goes
|
|
152
|
+
into the document's own payload, so `getText()` carries it and nothing is
|
|
153
|
+
uploaded anywhere. `toolbar: false` leaves out the toolbar and the context
|
|
154
|
+
menu, for a host with its own controls.
|
|
155
|
+
|
|
156
|
+
The editor behaves on a page as the view does: a plain wheel scrolls your
|
|
157
|
+
page, Ctrl or ⌘ with the wheel zooms, and any number of editors can share a
|
|
158
|
+
page. A plain click selects, so a link is followed on ⌘-click (Ctrl elsewhere),
|
|
159
|
+
and only an `https:` or `mailto:` one: to `onLink`, or else into a new tab
|
|
160
|
+
with `noopener`. `fit` frames the document when the editor opens, and again
|
|
161
|
+
when `el` changes size only until the person moves the camera.
|
|
162
|
+
|
|
163
|
+
The editor's chrome is drawn for the default dark `surface`. With a light
|
|
164
|
+
one, also set `--vd-page-label` and `--vd-page-label-hover` on `el` (the
|
|
165
|
+
colours of each page's name above its corner), for example to
|
|
166
|
+
`rgb(0 0 0 / 0.5)` and `#1a1a1f`.
|
|
167
|
+
|
|
168
|
+
## Fonts
|
|
169
|
+
|
|
170
|
+
By default (`fonts: 'exact'`) the view lays text out from the same font files
|
|
171
|
+
the app exports with, so lines break where the app breaks them. It draws at
|
|
172
|
+
once with your page's fonts, loads the font code (about 150 KB gzip, only now),
|
|
173
|
+
fetches only the faces the document uses (one `.woff2` per weight and style),
|
|
174
|
+
and lays the text out again once when they arrive. A document opened later
|
|
175
|
+
with `load()` has any new face fetched before it is drawn.
|
|
176
|
+
`handle.on('fonts', (state) => …)` says `'exact'` once the files are in use.
|
|
177
|
+
|
|
178
|
+
If a face cannot be fetched, the view stays on your page's fonts, `problems`
|
|
179
|
+
gains a `fonts-unavailable` warning and the `fonts` event says `'browser'`;
|
|
180
|
+
nothing throws. `fonts: 'browser'` skips all of this.
|
|
181
|
+
|
|
182
|
+
The editor measures the same way. A face that an edit brings in, such as a
|
|
183
|
+
word made bold, is fetched once the document has been still for a moment. If
|
|
184
|
+
one cannot be fetched the editor carries on with your page's fonts; the editor
|
|
185
|
+
has no `problems` or `fonts` event yet to say so.
|
|
186
|
+
|
|
187
|
+
### Where the faces come from
|
|
188
|
+
|
|
189
|
+
The faces come from `fontBase`, by default this package's `fonts/` folder on
|
|
190
|
+
jsDelivr (`https://cdn.jsdelivr.net/npm/@getv.design/embed@0.1.0/fonts/`). That means
|
|
191
|
+
your readers' browsers request the faces from jsDelivr, and a content security
|
|
192
|
+
policy needs it in `connect-src`. To keep those requests on your own origin,
|
|
193
|
+
copy `node_modules/@getv.design/embed/fonts/` to your site and point `fontBase` at it:
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
await view(el, text, { fontBase: '/assets/vdesign-fonts/' });
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The faces are Inter, under the SIL Open Font License (`fonts/OFL.txt`).
|
|
200
|
+
|
|
201
|
+
## Styles and content security policy
|
|
202
|
+
|
|
203
|
+
The embed's styles reach the page by one of two routes.
|
|
204
|
+
|
|
205
|
+
1. **Injected (the default).** The view inserts one
|
|
206
|
+
`<style id="vd-viewer-styles">` into the page's head, and the editor
|
|
207
|
+
another, `<style id="vd-editor-styles">`. Nothing to set up, but a
|
|
208
|
+
content security policy must allow `style-src 'unsafe-inline'`.
|
|
209
|
+
2. **Linked.** Serve the package's `view.css` (and `edit.css`, if you edit)
|
|
210
|
+
and link them under those ids. The view and the editor find the id and
|
|
211
|
+
insert nothing, so the page needs no `'unsafe-inline'`:
|
|
212
|
+
|
|
213
|
+
```html
|
|
214
|
+
<link rel="stylesheet" id="vd-viewer-styles" href="/view.css" />
|
|
215
|
+
<link rel="stylesheet" id="vd-editor-styles" href="/edit.css" />
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Link them before the embed runs, from your own origin or from the CDN
|
|
219
|
+
(`https://cdn.jsdelivr.net/npm/@getv.design/embed@0.1.0/view.css`). The id is what
|
|
220
|
+
the embed looks for: linked without it, the stylesheet still works, but
|
|
221
|
+
the embed inserts its `<style>` as well.
|
|
222
|
+
|
|
223
|
+
Either way, the theme is a set of CSS custom properties, which you can set on
|
|
224
|
+
the element you give the embed; `--vd-accent`, for example, colours the
|
|
225
|
+
selection.
|
|
226
|
+
|
|
227
|
+
The view, the player and the editor run under a policy with no
|
|
228
|
+
`'unsafe-inline'` once the styles are linked. `npm run embed:check` runs
|
|
229
|
+
every mode under this one, with the package served from the page's own
|
|
230
|
+
origin:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
What each part is for:
|
|
237
|
+
|
|
238
|
+
- `script-src`: wherever you serve the package's `dist/` from.
|
|
239
|
+
- `style-src`: wherever you serve `view.css` and `edit.css` from. Without them
|
|
240
|
+
linked, the embed injects a `<style>` and needs `'unsafe-inline'`.
|
|
241
|
+
- `img-src blob:` for a document's images, which are drawn from object URLs.
|
|
242
|
+
`img-src data:` for the editor, whose cursors are `data:` images.
|
|
243
|
+
- `connect-src`: the document URLs you pass, and `fontBase`. With the default
|
|
244
|
+
`fontBase`, that means `https://cdn.jsdelivr.net`; served yourself, your own
|
|
245
|
+
origin.
|
|
246
|
+
- `font-src` needs nothing for the embed: the faces are fetched as bytes and
|
|
247
|
+
handed to the page as `FontFace`s, which no `font-src` governs.
|
|
248
|
+
|
|
249
|
+
Served from jsDelivr instead, the same policy needs
|
|
250
|
+
`https://cdn.jsdelivr.net` in `script-src`, `style-src` and `connect-src`.
|
|
251
|
+
|
|
252
|
+
`renderSvg` output carries a `style="mix-blend-mode: …"` attribute on any
|
|
253
|
+
layer with a blend mode. Inlined into a page under `style-src 'self'`, those
|
|
254
|
+
layers lose their blend mode and draw normally; shown as an `<img>`, the SVG
|
|
255
|
+
is unaffected.
|
|
256
|
+
|
|
257
|
+
## Host page CSS
|
|
258
|
+
|
|
259
|
+
There is no shadow root, so your page's CSS reaches the embed. Every class it
|
|
260
|
+
uses starts with `vd-`, which keeps its rules and yours from colliding, but a
|
|
261
|
+
reset that targets elements still applies. `npm run embed:check` runs the
|
|
262
|
+
editor under a reset like a CSS framework's preflight (`svg { display: block }`,
|
|
263
|
+
`button { all: unset }`, `* { color: red }`, border-box sizing), and this is
|
|
264
|
+
what it does:
|
|
265
|
+
|
|
266
|
+
- **The drawing is unaffected.** A document's colours, sizes and fonts are
|
|
267
|
+
attributes on its SVG, which element selectors do not reach, and the theme
|
|
268
|
+
variables still apply: `--vd-accent` set on your element colours the
|
|
269
|
+
selection.
|
|
270
|
+
- **`* { color: … }` recolours the editor's chrome.** The toolbar's icons and
|
|
271
|
+
the context menu's labels take your colour, and a disabled menu item no
|
|
272
|
+
longer looks dimmed, because each element gets the colour directly rather
|
|
273
|
+
than inheriting the editor's.
|
|
274
|
+
- **`button { all: unset }` and border-box resets resize the chrome.** The
|
|
275
|
+
toolbar grows (348 by 32 pixels became 376 by 44), and the context menu's
|
|
276
|
+
shortcut column is cut off at its right edge. The view draws no buttons, so
|
|
277
|
+
only the editor is affected.
|
|
278
|
+
|
|
279
|
+
If your stylesheet has rules like these, scope them to your own content so
|
|
280
|
+
they do not match inside the element you give the embed.
|
|
281
|
+
|
|
282
|
+
## Not yet
|
|
283
|
+
|
|
284
|
+
- **Arrow keys do not move a selected shape** in the editor. They move a
|
|
285
|
+
path's points while you edit them, and nothing else yet; drag instead.
|
|
286
|
+
- **No `problems` or `fonts` event on the editor**, so a face it could not
|
|
287
|
+
fetch goes unreported (see "Fonts").
|
|
288
|
+
|
|
289
|
+
Not planned for the first release, in no particular order: shadow-root
|
|
290
|
+
isolation, the inspector and layers panels, a custom element, React or Vue
|
|
291
|
+
wrappers, an IIFE or UMD build, images in Node.
|
|
292
|
+
|
|
293
|
+
## Attribution
|
|
294
|
+
|
|
295
|
+
Not required. The notices in the code, the comment at the top of each file,
|
|
296
|
+
are the only credit the licence asks you to keep, and a bundler that moves
|
|
297
|
+
them into a notices file keeps them well enough. If you would like to say
|
|
298
|
+
where your designs come from, a "Made with vDesign" link to
|
|
299
|
+
https://getv.design is appreciated.
|
|
300
|
+
|
|
301
|
+
## Licence
|
|
302
|
+
|
|
303
|
+
Free to use under the vDesign Embed Licence, in `LICENSE`: commercial use and
|
|
304
|
+
unmodified redistribution are allowed, attribution is appreciated but not
|
|
305
|
+
required, and there is no watermark or licence key. Bundling, minifying and
|
|
306
|
+
code-splitting it are not modification. The code is proprietary: you may not
|
|
307
|
+
change it or pass on a changed copy. The `.vd` format itself is open, under
|
|
308
|
+
CC BY 4.0, and anyone may implement it: see https://getv.design/docs/licence/. Third-party code and fonts in the
|
|
309
|
+
package, and their licences in full, are listed in `THIRD_PARTY_NOTICES.txt`.
|
|
310
|
+
|
|
311
|
+
## Working on it
|
|
312
|
+
|
|
313
|
+
The name above is read from `publishName` in this workspace's `package.json`,
|
|
314
|
+
the only place it is written. `npm run embed:build` writes the package into
|
|
315
|
+
`out/`; `npm run embed:pack` builds it, packs it into a tarball in
|
|
316
|
+
`packages/embed/` (gitignored, never published), and prints the tarball's
|
|
317
|
+
contents.
|
|
318
|
+
`out/package.json` stays `"private": true` until the licence has been reviewed
|
|
319
|
+
(below).
|
|
320
|
+
|
|
321
|
+
`npm run embed:check` builds the package and runs it in Chromium
|
|
322
|
+
(Playwright, from `check/`): host pages under a strict content security policy
|
|
323
|
+
with no `'unsafe-inline'`, the styles linked as above and the faces served
|
|
324
|
+
from the same origin. It covers `/static`, `/view`, presenting and `/edit`,
|
|
325
|
+
exact fonts and their failure, the wheel, links, several embeds on one page,
|
|
326
|
+
`destroy()`, and the reset-heavy host above. It needs a browser
|
|
327
|
+
(`npx playwright install chromium`), so it is not part of `npm run verify`;
|
|
328
|
+
run it when you change this package or anything it bundles.
|
|
329
|
+
|
|
330
|
+
The build writes `THIRD_PARTY_NOTICES.txt` from esbuild's metafile
|
|
331
|
+
(`scripts/notices.mjs`), so it lists exactly the packages whose code ships. It
|
|
332
|
+
fails on a licence outside MIT, ISC, Apache-2.0, 0BSD, BSD-2-Clause,
|
|
333
|
+
BSD-3-Clause and OFL-1.1, and on a package with no licence file unless
|
|
334
|
+
`notices-overrides.json` says where its text came from (an override may also
|
|
335
|
+
fill a missing licence, never replace one). Code that is neither under
|
|
336
|
+
`node_modules` nor in one of our workspaces, such as a linked package, is
|
|
337
|
+
refused, and a refused build leaves no `out/`. Adding a licence to
|
|
338
|
+
that list is a licensing decision, not a build fix. Every published `.js` file
|
|
339
|
+
starts with a licence banner, and licence comments in bundled code are kept at
|
|
340
|
+
the end of the file.
|
|
341
|
+
|
|
342
|
+
## Publishing (owner only)
|
|
343
|
+
|
|
344
|
+
**This package is published only by `.github/workflows/publish-embed.yml`.
|
|
345
|
+
Nobody, person or agent, runs `npm publish` locally**; `--dry-run` is the one
|
|
346
|
+
exception.
|
|
347
|
+
|
|
348
|
+
On every merge to `main` that touches this package, the workspaces it bundles
|
|
349
|
+
(core, engine, renderer, fonts, viewer, editor), `package-lock.json` or the
|
|
350
|
+
workflow, the workflow installs, runs this package's tests and
|
|
351
|
+
`npm run embed:check`, builds `out/`, runs the publish gate, and then asks
|
|
352
|
+
`scripts/publish-decision.mjs`:
|
|
353
|
+
|
|
354
|
+
- **Publish:** the version in `package.json` is not on npm. The workflow
|
|
355
|
+
publishes the `out/` it just checked, with `--access public`, then waits up
|
|
356
|
+
to five minutes for jsDelivr to serve a font from it.
|
|
357
|
+
- **Skip:** that version is on npm, and the published tarball holds exactly
|
|
358
|
+
the files this build packs. Nothing that ships changed.
|
|
359
|
+
- **Bump needed:** that version is on npm with different files. The run
|
|
360
|
+
fails with "bump the version in packages/embed/package.json".
|
|
361
|
+
|
|
362
|
+
A pull request runs the same check, with no token in reach, and its summary
|
|
363
|
+
says which of the three its merge would do; a bump shows red there first. So a
|
|
364
|
+
PR that changes what ships, this README included, raises `version` in the same
|
|
365
|
+
PR. Locally, after `npm run embed:build`, run
|
|
366
|
+
`node packages/embed/scripts/publish-decision.mjs` to ask the same question.
|
|
367
|
+
|
|
368
|
+
The comparison works because the build is deterministic: two clean builds give
|
|
369
|
+
byte-identical `out/` and tarballs. Keep it so. A timestamp, an absolute path
|
|
370
|
+
or an unordered list in the output would make every merge look like a change.
|
|
371
|
+
|
|
372
|
+
The workflow authenticates with the `NPM_TOKEN` secret: a granular npm token
|
|
373
|
+
with read and write on the package's scope and nothing else, allowed to bypass
|
|
374
|
+
2FA. It expires; renew it before it does, or the next publish fails at the
|
|
375
|
+
publish step. There is no provenance, which npm makes only from a public
|
|
376
|
+
repository. Once the package exists, npm trusted publishing (OIDC) can replace
|
|
377
|
+
the token: register the workflow on npmjs.com as the package's trusted
|
|
378
|
+
publisher (it already has `id-token: write`), then delete the secret. Whether
|
|
379
|
+
npm accepts trusted publishing from a private repository is unconfirmed, so
|
|
380
|
+
try it with the token still in place.
|
|
381
|
+
|
|
382
|
+
### The licence gate
|
|
383
|
+
|
|
384
|
+
Nothing publishes until the licence has been reviewed by a lawyer and the owner
|
|
385
|
+
has committed a record of that review. `scripts/gate.mjs` is the lock: the build
|
|
386
|
+
writes `"private": false` into `out/package.json` only when it passes, the
|
|
387
|
+
workflow runs it before deciding, and the manifest's `prepublishOnly` runs it
|
|
388
|
+
again, so `npm publish` (and `npm publish --dry-run`) from `out/` fails while it
|
|
389
|
+
is closed. Notes for the reviewer are in `LICENSE-REVIEW.md`, which is not
|
|
390
|
+
packed.
|
|
391
|
+
|
|
392
|
+
**Agents never create or edit `legal-review.json`.** The record is the owner's
|
|
393
|
+
statement that the review happened. To make it:
|
|
394
|
+
|
|
395
|
+
1. Have `LICENSE` reviewed, with `LICENSE-REVIEW.md` as the brief. Make the
|
|
396
|
+
changes advised, and remove the `DRAFT` line at the top.
|
|
397
|
+
2. Hash the licence exactly as it will ship:
|
|
398
|
+
|
|
399
|
+
```sh
|
|
400
|
+
shasum -a 256 packages/embed/LICENSE
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
3. Commit `packages/embed/legal-review.json`, with the hash from step 2:
|
|
404
|
+
|
|
405
|
+
```json
|
|
406
|
+
{
|
|
407
|
+
"reviewed": true,
|
|
408
|
+
"by": "Name of the reviewer",
|
|
409
|
+
"date": "2026-11-02",
|
|
410
|
+
"licenceSha256": "the 64 hex digits shasum printed"
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
The workflow never publishes from a saved `.tgz` and never with
|
|
415
|
+
`--ignore-scripts`: npm runs `prepublishOnly` only when publishing a folder, so
|
|
416
|
+
either would skip the gate's second check and rely on `"private"` alone.
|
|
417
|
+
`.gitattributes` marks `packages/embed/LICENSE` as `-text`, so git never
|
|
418
|
+
rewrites its line endings and the hash is the same on every platform.
|
|
419
|
+
|
|
420
|
+
Any later edit to `LICENSE` closes the gate again until the record's hash is
|
|
421
|
+
updated, so the text that ships is always the text that was reviewed. Run
|
|
422
|
+
`node packages/embed/scripts/gate.mjs` to see whether the gate is open and why
|
|
423
|
+
not.
|