@codename/deck 0.1.2 → 0.2.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/README.md +1 -1
- package/dist/deck.bundle.mjs +75 -75
- package/dist/deck.bundle.mjs.map +3 -3
- package/dist/deck.css +10 -0
- package/dist/deck.js +80 -70
- package/dist/deck.js.map +3 -3
- package/dist/deck.mjs +34 -34
- package/dist/deck.mjs.map +3 -3
- package/dist/template.mjs +2 -0
- package/dist/template.mjs.map +7 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/kit.d.ts +10 -1
- package/dist/types/look.d.ts +2 -1
- package/dist/types/template.d.ts +16 -0
- package/dist/types/version.d.ts +1 -1
- package/docs/host.md +2 -0
- package/docs/kits.md +14 -2
- package/package.json +9 -5
- package/src/deck.css +10 -0
- package/src/deck.js +3 -1
- package/src/index.js +1 -1
- package/src/kit.js +32 -14
- package/src/look.js +5 -2
- package/src/pptx.js +6 -2
- package/src/render.js +9 -1
- package/src/standalone.js +1 -1
- package/src/template.js +161 -0
- package/src/version.js +1 -1
package/src/render.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// source (data-edit, offsets), so an edit made on the slide goes back into the Markdown. HTML a
|
|
5
5
|
// deck holds is not drawn: Slidev's <v-click> and <v-clicks> become steps, <br> a line break, and
|
|
6
6
|
// any other tag gives up its words alone. ```echarts and ```mermaid go to the host's drawers.
|
|
7
|
-
import { STANDARD, inferLayout, ALIASES } from './kit.js'
|
|
7
|
+
import { STANDARD, CORNERS, inferLayout, ALIASES } from './kit.js'
|
|
8
8
|
|
|
9
9
|
/** @typedef {import('./parse.js').Slide} Slide */
|
|
10
10
|
/** @typedef {import('./parse.js').Slot} Slot */
|
|
@@ -73,6 +73,14 @@ export function renderSlide(slide, context) {
|
|
|
73
73
|
// Slots a layout doesn't have still show, under the rest, rather than being lost.
|
|
74
74
|
for (const slot of slide.slots) if (!names.includes(slot.name)) canvas.append(h('div', { class: 'deck-slot', 'data-slot': slot.name }, blocks(slot, context, counter)))
|
|
75
75
|
if (definition.numbered && slide.settings.paginate !== false) section.append(h('span', { class: 'deck-number', 'aria-hidden': 'true' }, [context.number(slide.index + 1)]))
|
|
76
|
+
// The kit's logo in its corner, small, or big where the layout says (a cover, the end); its dark one beside it, which the
|
|
77
|
+
// stylesheet shows on dark paper, where without one the logo sits on a light plate.
|
|
78
|
+
const { logo, logoAt, dark } = context.kit.tokens, at = CORNERS.includes(logoAt) ? logoAt : CORNERS[0]
|
|
79
|
+
if (logo && definition.logo !== 'none') {
|
|
80
|
+
for (const [src, kind] of [[logo, dark?.logo ? 'has-dark' : ''], [dark?.logo, 'is-dark']]) {
|
|
81
|
+
if (src) section.append(h('img', { class: `deck-logo ${kind}`.trim(), src, alt: '', 'aria-hidden': 'true', 'data-size': definition.logo, 'data-at': at }))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
76
84
|
return { element: section, steps: counter.step }
|
|
77
85
|
}
|
|
78
86
|
|
package/src/standalone.js
CHANGED
|
@@ -11,7 +11,7 @@ import css from './deck.css'
|
|
|
11
11
|
const me = /** @type {HTMLScriptElement | null} */ (document.currentScript)
|
|
12
12
|
const base = me?.dataset.base ?? 'https://cdn.jsdelivr.net/npm/'
|
|
13
13
|
|
|
14
|
-
const PAGE = `html, body { margin: 0; } body { min-block-size: 100vh; }
|
|
14
|
+
const PAGE = /* css */ `html, body { margin: 0; } body { min-block-size: 100vh; }
|
|
15
15
|
body > .deck { min-block-size: 100vh; padding: clamp(0.5rem, 3vw, 2rem); align-content: center; justify-items: center; background: color-mix(in srgb, var(--deck-ink) 7%, var(--deck-paper)); }
|
|
16
16
|
body > .deck > .deck-stage, body > .deck > .deck-bar { inline-size: min(100%, (100vh - 7rem) * 16 / 9); }
|
|
17
17
|
body > .deck.is-editing { justify-items: stretch; align-content: start; }
|
package/src/template.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// A kit from a company's PowerPoint template (.potx, .pptx, or an Office theme, .thmx), and a kit's tokens out in the W3C's Design Tokens format:
|
|
2
|
+
// a module of its own (@codename/deck/template), which a host loads only when it reads or writes one. From the template,
|
|
3
|
+
// its theme's colours (the first light its paper, the first dark its ink, the first accent its primary, the six accents a
|
|
4
|
+
// chart's colours) and faces (headings', words'); the first picture on its slide master, its logo, in the corner it sits
|
|
5
|
+
// in; and each slide layout, a layout of the kit by its name, its placeholders a grid of slots, on the paper its
|
|
6
|
+
// background is. The file is read with the platform's own DecompressionStream and its XML with regular expressions, as a
|
|
7
|
+
// page under Trusted Types parses no markup. What can't be read is left out, with a reason.
|
|
8
|
+
|
|
9
|
+
import { STANDARD, mergeKits, logoOf } from './kit.js'
|
|
10
|
+
|
|
11
|
+
/** The slide layouts PowerPoint names as deck's own are: a template's take their place. */
|
|
12
|
+
const OWN = /** @type {Record<string, string>} */ ({ 'Title Slide': 'cover', 'Title and Content': 'default', 'Section Header': 'section', 'Two Content': 'two-cols-header', 'Title Only': 'center', Blank: 'none' })
|
|
13
|
+
const PPTX = ['Title Slide', 'Title and Content', 'Section Header', 'Two Content', 'Comparison', 'Title Only', 'Blank', 'Content with Caption', 'Picture with Caption']
|
|
14
|
+
const PICTURES = /** @type {Record<string, string>} */ ({ png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', svg: 'image/svg+xml' })
|
|
15
|
+
|
|
16
|
+
/** A zip's files by name, each read when asked. @param {Uint8Array} bytes */
|
|
17
|
+
function unzip(bytes) {
|
|
18
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
19
|
+
let end = bytes.length - 22
|
|
20
|
+
while (end >= 0 && view.getUint32(end, true) !== 0x06054b50) end--
|
|
21
|
+
if (end < 0) throw new Error('not a PowerPoint file')
|
|
22
|
+
/** @type {Map<string, () => Promise<Uint8Array>>} */
|
|
23
|
+
const files = new Map()
|
|
24
|
+
for (let at = view.getUint32(end + 16, true), n = view.getUint16(end + 10, true); n > 0; n--) {
|
|
25
|
+
const method = view.getUint16(at + 10, true), size = view.getUint32(at + 20, true), name = new TextDecoder().decode(bytes.subarray(at + 46, at + 46 + view.getUint16(at + 28, true)))
|
|
26
|
+
const local = view.getUint32(at + 42, true), start = local + 30 + view.getUint16(local + 26, true) + view.getUint16(local + 28, true), data = bytes.subarray(start, start + size)
|
|
27
|
+
files.set(name, async () => (method === 0 ? data : new Uint8Array(await new Response(new Blob([/** @type {BlobPart} */ (data)]).stream().pipeThrough(new DecompressionStream('deflate-raw'))).arrayBuffer())))
|
|
28
|
+
at += 46 + view.getUint16(at + 28, true) + view.getUint16(at + 30, true) + view.getUint16(at + 32, true)
|
|
29
|
+
}
|
|
30
|
+
return files
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** An attribute's value in a piece of XML. @param {string} xml @param {string} name */
|
|
34
|
+
const attr = (xml, name) => new RegExp(`\\b${name}="([^"]*)"`).exec(xml)?.[1]
|
|
35
|
+
/** A part's relationships, by their ids, as paths from the package's root. @param {string} rels @param {string} from the part's folder */
|
|
36
|
+
const targets = (rels, from) => new Map([...rels.matchAll(/<Relationship\b[^>]*>/g)].map(([one]) => [String(attr(one, 'Id')), new URL(String(attr(one, 'Target')), `file:///${from}/`).pathname.slice(1)]))
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A kit read from a PowerPoint template, with what couldn't be read.
|
|
40
|
+
* @param {Uint8Array} bytes a .potx, .pptx or .thmx @param {{ name?: string }} [options]
|
|
41
|
+
* @returns {Promise<{ kit: import('./kit.js').Kit, problems: string[] }>}
|
|
42
|
+
*/
|
|
43
|
+
export async function kitFromTemplate(bytes, { name } = {}) {
|
|
44
|
+
const files = unzip(bytes), problems = /** @type {string[]} */ ([])
|
|
45
|
+
const text = async (/** @type {string} */ path) => new TextDecoder().decode(await (files.get(path)?.() ?? new Uint8Array()))
|
|
46
|
+
// A template's parts are under ppt/, an Office theme's under theme/.
|
|
47
|
+
const master = [...files.keys()].filter((path) => /^(ppt|theme)\/slideMasters\/slideMaster\d+\.xml$/.test(path)).sort()[0]
|
|
48
|
+
if (!master) throw new Error('no slide master: not a PowerPoint template')
|
|
49
|
+
const root = master.split('/')[0], masterXml = await text(master), masterRels = targets(await text(master.replace(/([^/]+)$/, '_rels/$1.rels')), `${root}/slideMasters`)
|
|
50
|
+
const themePath = [...masterRels.values()].find((path) => /theme\/theme\d+\.xml$/.test(path)) ?? `${root}/theme/theme1.xml`, theme = await text(themePath)
|
|
51
|
+
const [cx, cy] = ((size) => [Number(attr(size, 'cx') ?? 12192000), Number(attr(size, 'cy') ?? 6858000)])(/<p:sldSz\b[^>]*>/.exec(await text(`${root}/presentation.xml`))?.[0] ?? '')
|
|
52
|
+
|
|
53
|
+
// Its theme: colours by their role, faces for headings and words.
|
|
54
|
+
const scheme = Object.fromEntries([...theme.matchAll(/<a:(dk1|lt1|dk2|lt2|accent[1-6])>\s*<a:(?:srgbClr\b[^>]*\bval|sysClr\b[^>]*\blastClr)="([0-9A-Fa-f]{6})"/g)].map(([, role, value]) => [role, `#${value.toLowerCase()}`]))
|
|
55
|
+
const face = (/** @type {string} */ part) => attr(new RegExp(`<a:${part}>[\\s\\S]*?<a:latin\\b[^>]*>`).exec(theme)?.[0].replace(/^[\s\S]*<a:latin/, '') ?? '', 'typeface')
|
|
56
|
+
const [heading, body] = [face('majorFont'), face('minorFont')].map((one) => (one && !one.startsWith('+') ? [one, 'ui-sans-serif', 'system-ui', 'sans-serif'] : null))
|
|
57
|
+
/** @type {Record<string, any>} */
|
|
58
|
+
const tokens = { ...(scheme.lt1 && { paper: scheme.lt1 }), ...(scheme.dk1 && { ink: scheme.dk1 }), ...(scheme.accent1 && { primary: scheme.accent1 }), ...(scheme.dk2 && { muted: scheme.dk2 }), ...(scheme.lt2 && { soft: scheme.lt2 }) }
|
|
59
|
+
const accents = [1, 2, 3, 4, 5, 6].map((n) => scheme[`accent${n}`]).filter(Boolean)
|
|
60
|
+
if (accents.length) tokens.series = accents
|
|
61
|
+
if (heading || body) tokens.fonts = { heading: heading ?? body, body: body ?? heading }
|
|
62
|
+
|
|
63
|
+
// Its logo: the first picture on its master that a browser draws, in the corner it sits in.
|
|
64
|
+
for (const [pic] of masterXml.matchAll(/<p:pic>[\s\S]*?<\/p:pic>/g)) {
|
|
65
|
+
const path = masterRels.get(String(attr(/<a:blip\b[^>]*>/.exec(pic)?.[0] ?? '', 'r:embed'))), type = PICTURES[/\.(\w+)$/.exec(path ?? '')?.[1].toLowerCase() ?? '']
|
|
66
|
+
if (!path || !type || !files.has(path)) continue
|
|
67
|
+
const data = await files.get(path)?.(), url = data && `data:${type};base64,${base64(data)}`
|
|
68
|
+
if (!logoOf(url)) { problems.push(`the logo ${path.split('/').pop()} is larger than 512 KB`); break }
|
|
69
|
+
const [x, y] = ((off) => [Number(attr(off, 'x') ?? 0), Number(attr(off, 'y') ?? 0)])(/<a:off\b[^>]*>/.exec(pic)?.[0] ?? '')
|
|
70
|
+
Object.assign(tokens, { logo: url, logoAt: `${y < cy / 2 ? 'top' : 'bottom'}-${x < cx / 2 ? 'start' : 'end'}` })
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Its layouts: each placeholder where it sits, else where the master's of its kind sits.
|
|
75
|
+
const placed = (/** @type {string} */ xml) => [...xml.matchAll(/<p:sp>[\s\S]*?<\/p:sp>/g)].flatMap(([sp]) => {
|
|
76
|
+
const ph = /<p:ph\b[^>]*>/.exec(sp)?.[0]
|
|
77
|
+
if (!ph) return []
|
|
78
|
+
const off = /<a:off\b[^>]*>/.exec(sp)?.[0], ext = /<a:ext\b[^>]*>/.exec(sp)?.[0]
|
|
79
|
+
return [{ type: attr(ph, 'type') ?? 'body', name: attr(/<p:cNvPr\b[^>]*>/.exec(sp)?.[0] ?? '', 'name') ?? '', box: off && ext ? [Number(attr(off, 'x')), Number(attr(off, 'y')), Number(attr(ext, 'cx')), Number(attr(ext, 'cy'))] : null }]
|
|
80
|
+
})
|
|
81
|
+
const masterBoxes = placed(masterXml)
|
|
82
|
+
const paperOf = (/** @type {string} */ xml) => {
|
|
83
|
+
const fill = /<p:bg>[\s\S]*?<\/p:bg>/.exec(xml)?.[0] ?? '', role = attr(/<a:schemeClr\b[^>]*>/.exec(fill)?.[0] ?? '', 'val'), rgb = attr(/<a:srgbClr\b[^>]*>/.exec(fill)?.[0] ?? '', 'val')?.toLowerCase()
|
|
84
|
+
return role === 'accent1' || (rgb && `#${rgb}` === scheme.accent1) ? 'fill' : role === 'tx1' || role === 'dk1' || (rgb && `#${rgb}` === scheme.dk1) ? 'ink' : role === 'bg2' || role === 'lt2' ? 'soft' : 'paper'
|
|
85
|
+
}
|
|
86
|
+
/** @type {Record<string, any>} */
|
|
87
|
+
const layouts = {}
|
|
88
|
+
const paths = [...masterRels.values()].filter((path) => /slideLayouts\/slideLayout\d+\.xml$/.test(path)).sort((a, b) => Number(/(\d+)\.xml$/.exec(a)?.[1]) - Number(/(\d+)\.xml$/.exec(b)?.[1]))
|
|
89
|
+
for (const path of paths) {
|
|
90
|
+
const xml = await text(path), title = (attr(/<p:cSld\b[^>]*>/.exec(xml)?.[0] ?? '', 'name') ?? '').replace(/&/g, '&').trim()
|
|
91
|
+
const boxes = placed(xml).filter((one) => !['dt', 'ftr', 'sldNum'].includes(one.type)).map((one) => ({ ...one, box: one.box ?? masterBoxes.find((m) => m.type === one.type || (one.type === 'ctrTitle' && m.type === 'title'))?.box ?? null }))
|
|
92
|
+
.filter((one) => one.box)
|
|
93
|
+
const key = OWN[title] ?? (title.toLowerCase().normalize('NFKD').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').replace(/^(?=\d)/, 'l-').slice(0, 32) || `layout-${paths.indexOf(path) + 1}`)
|
|
94
|
+
const layout = gridOf(boxes, cx, cy)
|
|
95
|
+
if (!layout) { problems.push(`layout ${title || key}: its placeholders make no grid deck lays out`); continue }
|
|
96
|
+
layouts[key] = { label: title || key, ...layout, paper: paperOf(xml), title: boxes.some((one) => one.type === 'ctrTitle') ? 'huge' : 'normal', logo: STANDARD.layouts[key]?.logo ?? 'small',
|
|
97
|
+
...(boxes.some((one) => one.type === 'ctrTitle') && { justify: 'center' }), pptx: PPTX.includes(title) ? title : layout.slots.length > 1 ? 'Comparison' : 'Title and Content',
|
|
98
|
+
numbered: /<p:ph\b[^>]*type="sldNum"/.test(xml) }
|
|
99
|
+
}
|
|
100
|
+
const merged = mergeKits({ name: name ?? 'template', tokens, layouts })
|
|
101
|
+
return { kit: { ...merged.kit, name: name ?? 'template' }, problems: [...problems, ...merged.problems] }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Placeholders as a grid of slots: the titles the default slot, with the box under them across the same columns; the other
|
|
106
|
+
* boxes by the names deck gave them (`body-right`, as its PowerPoint writes them), else `left`, `middle` and `right` side by
|
|
107
|
+
* side, else `s1`, `s2`…; the margins left to deck. @param {{ type: string, name: string, box: number[] | null }[]} boxes
|
|
108
|
+
* @param {number} cx @param {number} cy
|
|
109
|
+
*/
|
|
110
|
+
function gridOf(boxes, cx, cy) {
|
|
111
|
+
const isTitle = (/** @type {{ type: string }} */ one) => ['title', 'ctrTitle', 'subTitle'].includes(one.type)
|
|
112
|
+
const box = (/** @type {{ box: number[] | null }} */ one) => /** @type {number[]} */ (one.box)
|
|
113
|
+
const across = (/** @type {number[]} */ a, /** @type {number[]} */ b) => Math.abs(a[0] - b[0]) < cx * 0.05 && Math.abs(a[2] - b[2]) < cx * 0.05 && a[1] > b[1]
|
|
114
|
+
const heading = boxes.filter(isTitle), own = (/** @type {{ name: string }} */ one) => /^body-([a-z][a-z0-9-]{0,31})$/.exec(one.name)?.[1]
|
|
115
|
+
const sorted = boxes.filter((one) => !isTitle(one)).sort((a, b) => box(a)[0] - box(b)[0] || box(a)[1] - box(b)[1])
|
|
116
|
+
// The heading's slot's own words: the only box, or the one under the heading across its columns.
|
|
117
|
+
const under = !heading.length ? undefined : sorted.length === 1 && !own(sorted[0]) ? sorted[0] : sorted.find((one) => own(one) === 'default' || (!own(one) && across(box(one), box(heading[0]))))
|
|
118
|
+
const titles = under ? [...heading, under] : heading, others = sorted.filter((one) => one !== under)
|
|
119
|
+
const union = (/** @type {number[][]} */ list) => (list.length ? ((xs, ys, xe, ye) => [xs, ys, xe - xs, ye - ys])(Math.min(...list.map((b) => b[0])), Math.min(...list.map((b) => b[1])),
|
|
120
|
+
Math.max(...list.map((b) => b[0] + b[2])), Math.max(...list.map((b) => b[1] + b[3]))) : null)
|
|
121
|
+
// The default slot: the heading and, when it is the only one, the box under it; else the heading, else the first box.
|
|
122
|
+
const rest = titles.length ? others : others.slice(1)
|
|
123
|
+
const first = titles.length ? /** @type {number[]} */ (union(titles.map(box))) : others.length ? box(others[0]) : [0, 0, cx, cy]
|
|
124
|
+
const side = rest.every((one, i) => !i || box(one)[0] >= (box(rest[i - 1])[0] + box(rest[i - 1])[2]) * 0.98)
|
|
125
|
+
const names = side && rest.length === 2 && titles.length ? ['left', 'right'] : side && rest.length === 3 && titles.length ? ['left', 'middle', 'right'] : side && rest.length === 1 ? ['right'] : side && rest.length === 2 ? ['middle', 'right']
|
|
126
|
+
: rest.map((_, i) => `s${i + 1}`)
|
|
127
|
+
/** @type {[string, number[]][]} */
|
|
128
|
+
const slots = [['default', first], ...rest.map((one, i) => /** @type {[string, number[]]} */ ([own(one) ?? names[i], box(one)]))]
|
|
129
|
+
// The grid's lines, near ones as one, then each cell the slot whose box holds its middle.
|
|
130
|
+
const lines = (/** @type {number} */ from, /** @type {number} */ size, /** @type {number} */ total) => [...new Set(slots.flatMap(([, b]) => [b[from], b[from] + b[size]]))].sort((a, b) => a - b)
|
|
131
|
+
.reduce((/** @type {number[]} */ kept, at) => (kept.length && at - kept[kept.length - 1] < total * 0.03 ? kept : [...kept, at]), [])
|
|
132
|
+
const xs = lines(0, 2, cx), ys = lines(1, 3, cy)
|
|
133
|
+
const holds = (/** @type {number[]} */ b, /** @type {number} */ x, /** @type {number} */ y) => x >= b[0] - cx * 0.03 && x <= b[0] + b[2] + cx * 0.03 && y >= b[1] - cy * 0.03 && y <= b[1] + b[3] + cy * 0.03
|
|
134
|
+
const areas = ys.slice(1).map((y, r) => xs.slice(1).map((x, c) => slots.find(([, b]) => holds(b, (xs[c] + x) / 2, (ys[r] + y) / 2))?.[0] ?? '.'))
|
|
135
|
+
if (!areas.length || !areas[0].length) return null
|
|
136
|
+
const fr = (/** @type {number[]} */ edges, /** @type {number} */ total) => (edges.length === 2 ? ['1fr'] : edges.slice(1).map((at, i) => `${Math.max(0.1, Math.round(((at - edges[i]) / total) * 100) / 10)}fr`))
|
|
137
|
+
// A row that holds only the heading takes what it needs.
|
|
138
|
+
const rows = fr(ys, cy).map((track, r) => (slots.length > 1 && areas[r].every((cell) => cell === 'default' || cell === '.') ? 'auto' : track))
|
|
139
|
+
return { slots: slots.map(([one]) => one), areas: areas.map((row) => row.join(' ')).join(' | '), columns: fr(xs, cx).join(' '), rows: rows.join(' ') }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Bytes as base64, a piece at a time. @param {Uint8Array} bytes */
|
|
143
|
+
function base64(bytes) {
|
|
144
|
+
let binary = ''
|
|
145
|
+
for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000))
|
|
146
|
+
return btoa(binary)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A kit's tokens in the W3C's Design Tokens format (DTCG 2025.10), as design tools read them: its colours light and
|
|
151
|
+
* dark, its chart colours and its faces; `tokensFrom()` reads them back. @param {Record<string, any>} tokens
|
|
152
|
+
*/
|
|
153
|
+
export function tokensToDtcg(tokens) {
|
|
154
|
+
const colour = (/** @type {unknown} */ value) => (typeof value === 'string' ? { $type: 'color', $value: value } : undefined)
|
|
155
|
+
const group = (/** @type {Record<string, any>} */ own) => Object.fromEntries(Object.entries({ bg: own.paper, fg: own.ink, accent: own.primary, muted: own.muted, surface: own.soft }).flatMap(([key, value]) => (colour(value) ? [[key, colour(value)]] : [])))
|
|
156
|
+
const fonts = tokens.fonts ?? {}
|
|
157
|
+
return {
|
|
158
|
+
color: { light: group(tokens), ...(tokens.dark && { dark: group(tokens.dark) }), ...(tokens.series && { series: Object.fromEntries(tokens.series.map((/** @type {string} */ one, /** @type {number} */ i) => [`s${i + 1}`, colour(one)])) }) },
|
|
159
|
+
...((fonts.heading || fonts.body) && { font: { ...(fonts.heading && { heading: { $type: 'fontFamily', $value: fonts.heading } }), ...(fonts.body && { body: { $type: 'fontFamily', $value: fonts.body } }) } }),
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// deck's version, which a linked file names in its renderer's address. Kept equal to package.json's by a test.
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.2.0'
|