@contraagent/elements 0.1.0 → 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.
Files changed (61) hide show
  1. package/README.md +16 -5
  2. package/custom-elements.json +1107 -345
  3. package/dist/code/code.d.ts +83 -0
  4. package/dist/code/code.d.ts.map +1 -0
  5. package/dist/code/code.js +206 -0
  6. package/dist/code/code.js.map +1 -0
  7. package/dist/code/highlight.d.ts +25 -0
  8. package/dist/code/highlight.d.ts.map +1 -0
  9. package/dist/code/highlight.js +312 -0
  10. package/dist/code/highlight.js.map +1 -0
  11. package/dist/code/index.d.ts +3 -0
  12. package/dist/code/index.d.ts.map +1 -0
  13. package/dist/code/index.js +5 -0
  14. package/dist/code/index.js.map +1 -0
  15. package/dist/define/all.d.ts +2 -0
  16. package/dist/define/all.d.ts.map +1 -1
  17. package/dist/define/all.js +2 -0
  18. package/dist/define/all.js.map +1 -1
  19. package/dist/define/code.d.ts +4 -0
  20. package/dist/define/code.d.ts.map +1 -0
  21. package/dist/define/code.js +7 -0
  22. package/dist/define/code.js.map +1 -0
  23. package/dist/define/tree.d.ts +4 -0
  24. package/dist/define/tree.d.ts.map +1 -0
  25. package/dist/define/tree.js +7 -0
  26. package/dist/define/tree.js.map +1 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/markdown/highlight.d.ts +1 -4
  32. package/dist/markdown/highlight.d.ts.map +1 -1
  33. package/dist/markdown/highlight.js +2 -85
  34. package/dist/markdown/highlight.js.map +1 -1
  35. package/dist/styles/prose.d.ts.map +1 -1
  36. package/dist/styles/prose.js +5 -0
  37. package/dist/styles/prose.js.map +1 -1
  38. package/dist/tree/index.d.ts +2 -0
  39. package/dist/tree/index.d.ts.map +1 -0
  40. package/dist/tree/index.js +4 -0
  41. package/dist/tree/index.js.map +1 -0
  42. package/dist/tree/tree.d.ts +83 -0
  43. package/dist/tree/tree.d.ts.map +1 -0
  44. package/dist/tree/tree.js +251 -0
  45. package/dist/tree/tree.js.map +1 -0
  46. package/docs/code.md +55 -0
  47. package/docs/examples/code.ts +28 -0
  48. package/docs/examples/tree.ts +44 -0
  49. package/docs/tree.md +44 -0
  50. package/package.json +19 -3
  51. package/src/code/code.ts +199 -0
  52. package/src/code/highlight.ts +378 -0
  53. package/src/code/index.ts +4 -0
  54. package/src/define/all.ts +2 -0
  55. package/src/define/code.ts +9 -0
  56. package/src/define/tree.ts +9 -0
  57. package/src/index.ts +2 -0
  58. package/src/markdown/highlight.ts +2 -94
  59. package/src/styles/prose.ts +5 -0
  60. package/src/tree/index.ts +3 -0
  61. package/src/tree/tree.ts +263 -0
@@ -0,0 +1,251 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html, nothing, svg } from 'lit';
8
+ import { property, state } from 'lit/decorators.js';
9
+ import { emit } from '../core/events.js';
10
+ import { withDefaults } from '../core/strings.js';
11
+ import { statusChip } from '../primitives/pill.js';
12
+ import { base } from '../styles/index.js';
13
+ export const DEFAULT_TREE_STRINGS = { empty: 'No items' };
14
+ const hasChildren = (t) => !!t.children?.length;
15
+ const selectable = (t) => t.selectable ?? !hasChildren(t);
16
+ /** The items shown, top to bottom: every item whose ancestors are all open (`isOpen`). */
17
+ export function visibleRows(items, isOpen) {
18
+ const out = [];
19
+ const walk = (list, level, parent) => list.forEach((item, index) => {
20
+ out.push({ item, level, parent, index, siblings: list.length });
21
+ if (hasChildren(item) && isOpen(item))
22
+ walk(item.children, level + 1, item.id);
23
+ });
24
+ walk(items, 1, null);
25
+ return out;
26
+ }
27
+ /** The ids of the ancestors of item `id`, outermost first; null when no item has that id. */
28
+ export function ancestorsOf(items, id) {
29
+ for (const t of items) {
30
+ if (t.id === id)
31
+ return [];
32
+ const below = t.children ? ancestorsOf(t.children, id) : null;
33
+ if (below)
34
+ return [t.id, ...below];
35
+ }
36
+ return null;
37
+ }
38
+ /** The first item, depth first, that `match` accepts. */
39
+ export function findItem(items, match) {
40
+ for (const t of items) {
41
+ if (match(t))
42
+ return t;
43
+ const below = t.children ? findItem(t.children, match) : null;
44
+ if (below)
45
+ return below;
46
+ }
47
+ return null;
48
+ }
49
+ const FOLDER = svg `<path d="M1.5 3.5h4l1.5 1.5h7.5v8h-13z" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"></path>`;
50
+ const FILE = svg `<path d="M3.5 1.5h6l3 3v10h-9z M9.5 1.5v3h3" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"></path>`;
51
+ /**
52
+ * Items in a tree, the children of each under it, folded and opened by the reader: files
53
+ * and directories, say. It is one tab stop: Up and Down move between the items shown,
54
+ * Right opens an item (then moves to its first child), Left closes it (or moves to its
55
+ * parent), Home and End go to the first and the last, a letter to the next item of
56
+ * that initial; Enter, Space or a click selects a selectable item (`cae-select`) and
57
+ * opens or closes any other. The toggle before an item with children opens and closes
58
+ * it (`cae-toggle`). The ancestors of `selected` are always open.
59
+ *
60
+ * @tagname cae-tree
61
+ * @fires cae-select - `{id}`: an item was chosen with a click, Enter or Space
62
+ * @fires cae-toggle - `{id, open}`: the reader opened or closed an item
63
+ * @csspart tree - the list of items
64
+ * @csspart item - an item's row
65
+ * @csspart empty - what it shows with no items
66
+ */
67
+ export class CaeTree extends LitElement {
68
+ constructor() {
69
+ super(...arguments);
70
+ this.items = [];
71
+ /** The selected item's id, or null. */
72
+ this.selected = null;
73
+ /** What the tree is named to a screen reader. */
74
+ this.label = '';
75
+ /** The visible words; English by default. */
76
+ this.strings = {};
77
+ /** The reader's choice of open and closed items, by id, over each item's `open`. */
78
+ this.opened = new Map();
79
+ /** The item that holds the tab stop. */
80
+ this.focusId = null;
81
+ this.isOpen = (t) => this.opened.get(t.id) ?? t.open ?? true;
82
+ }
83
+ static { this.styles = [
84
+ base,
85
+ css `
86
+ :host { display: block; font-size: var(--font-size-md); }
87
+ .tree { margin: 0; padding: 2px 0; }
88
+ .row { display: flex; align-items: center; gap: 4px; min-height: 24px; padding: 0 8px 0 calc((var(--level) - 1) * 14px + 4px);
89
+ border-radius: var(--radius-sm); cursor: pointer; white-space: nowrap; color: var(--text-2); }
90
+ .row:hover { background: var(--raise); }
91
+ .row[aria-selected='true'] { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--text); box-shadow: inset 2px 0 0 var(--accent); }
92
+ .row:focus-visible { outline-offset: -2px; }
93
+ .twist { flex: none; display: inline-grid; place-items: center; width: 16px; height: 16px; color: var(--text-muted); border-radius: var(--radius-sm); }
94
+ .twist svg { transition: transform 0.12s ease; }
95
+ [aria-expanded='true'] > .twist svg { transform: rotate(90deg); }
96
+ .twist:hover { color: var(--text); }
97
+ .icon { flex: none; width: 16px; height: 16px; color: var(--text-muted); }
98
+ .name { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
99
+ .badge { flex: none; margin-left: 6px; font-size: 11px; padding: 0 6px; border-radius: var(--radius-pill); background: var(--raise); color: var(--text-2); }
100
+ .badge[data-tone] { background: var(--tone-bg); color: var(--tone); }
101
+ .badge[data-tone='done'] { background: none; color: var(--text-2); }
102
+ .empty { color: var(--text-muted); font-size: var(--font-size-sm); padding: 4px 8px; }
103
+ @media (prefers-reduced-motion: reduce) { .twist svg { transition: none; } }
104
+ `,
105
+ ]; }
106
+ willUpdate(changed) {
107
+ if (changed.has('selected') && this.selected) {
108
+ const up = ancestorsOf(this.items, this.selected);
109
+ if (up?.some((id) => this.opened.get(id) !== true)) {
110
+ const next = new Map(this.opened);
111
+ for (const id of up)
112
+ next.set(id, true);
113
+ this.opened = next;
114
+ }
115
+ }
116
+ }
117
+ updated(changed) {
118
+ if (changed.has('selected') && this.selected && !this.matches(':focus-within')) {
119
+ this.rowOf(this.selected)?.scrollIntoView?.({ block: 'nearest' });
120
+ }
121
+ }
122
+ rowOf(id) {
123
+ return this.renderRoot.querySelector(`[data-id="${CSS.escape(id)}"]`);
124
+ }
125
+ setOpen(t, open) {
126
+ if (!hasChildren(t) || this.isOpen(t) === open)
127
+ return;
128
+ this.opened = new Map(this.opened).set(t.id, open);
129
+ emit(this, 'cae-toggle', { id: t.id, open });
130
+ }
131
+ choose(t) {
132
+ this.focusId = t.id;
133
+ if (selectable(t))
134
+ emit(this, 'cae-select', { id: t.id });
135
+ else
136
+ this.setOpen(t, !this.isOpen(t));
137
+ }
138
+ async moveTo(id) {
139
+ this.focusId = id;
140
+ await this.updateComplete;
141
+ this.rowOf(id)?.focus();
142
+ }
143
+ onKey(e, rows, i) {
144
+ const row = rows[i];
145
+ const t = row.item;
146
+ let to;
147
+ switch (e.key) {
148
+ case 'ArrowDown':
149
+ to = rows[i + 1];
150
+ break;
151
+ case 'ArrowUp':
152
+ to = rows[i - 1];
153
+ break;
154
+ case 'Home':
155
+ to = rows[0];
156
+ break;
157
+ case 'End':
158
+ to = rows[rows.length - 1];
159
+ break;
160
+ case 'ArrowRight':
161
+ if (hasChildren(t) && !this.isOpen(t))
162
+ this.setOpen(t, true);
163
+ else if (hasChildren(t))
164
+ to = rows[i + 1];
165
+ break;
166
+ case 'ArrowLeft':
167
+ if (hasChildren(t) && this.isOpen(t))
168
+ this.setOpen(t, false);
169
+ else if (row.parent)
170
+ to = rows.find((r) => r.item.id === row.parent);
171
+ break;
172
+ case 'Enter':
173
+ case ' ':
174
+ this.choose(t);
175
+ break;
176
+ default: {
177
+ if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey || !/\S/.test(e.key))
178
+ return;
179
+ const k = e.key.toLowerCase();
180
+ const order = [...rows.slice(i + 1), ...rows.slice(0, i)];
181
+ to = order.find((r) => r.item.name.toLowerCase().startsWith(k));
182
+ if (!to)
183
+ return;
184
+ }
185
+ }
186
+ e.preventDefault();
187
+ if (to)
188
+ void this.moveTo(to.item.id);
189
+ }
190
+ render() {
191
+ if (!this.items.length)
192
+ return html `<div class="empty" part="empty">${withDefaults(DEFAULT_TREE_STRINGS, this.strings).empty}</div>`;
193
+ const rows = visibleRows(this.items, this.isOpen);
194
+ const stop = rows.find((r) => r.item.id === this.focusId) ?? rows.find((r) => r.item.id === this.selected) ?? rows[0];
195
+ return html `<div class="tree" role="tree" part="tree" aria-label=${this.label || nothing}>
196
+ ${rows.map((r, i) => this.row(r, i, rows, r === stop))}
197
+ </div>`;
198
+ }
199
+ row(r, i, rows, stop) {
200
+ const t = r.item;
201
+ const parent = hasChildren(t);
202
+ const open = parent && this.isOpen(t);
203
+ const directory = (t.icon ?? (parent ? 'directory' : 'file')) === 'directory';
204
+ return html `<div
205
+ class="row"
206
+ part="item"
207
+ role="treeitem"
208
+ data-id=${t.id}
209
+ tabindex=${stop ? 0 : -1}
210
+ style="--level: ${r.level}"
211
+ aria-level=${r.level}
212
+ aria-setsize=${r.siblings}
213
+ aria-posinset=${r.index + 1}
214
+ aria-expanded=${parent ? String(open) : nothing}
215
+ aria-selected=${selectable(t) ? String(t.id === this.selected) : nothing}
216
+ @click=${() => this.choose(t)}
217
+ @focus=${() => (this.focusId = t.id)}
218
+ @keydown=${(e) => this.onKey(e, rows, i)}
219
+ >
220
+ <span class="twist" aria-hidden="true" @click=${(e) => {
221
+ if (!parent)
222
+ return;
223
+ e.stopPropagation();
224
+ this.focusId = t.id;
225
+ this.setOpen(t, !open);
226
+ }}>${parent ? html `<svg width="10" height="10" viewBox="0 0 10 10"><path d="M3 1.5 7 5 3 8.5" fill="none" stroke="currentColor" stroke-width="1.5"></path></svg>` : nothing}</span>
227
+ <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">${directory ? FOLDER : FILE}</svg>
228
+ <span class="name">${t.name}</span>
229
+ ${t.badge ? statusChip('badge', { text: t.badge, tone: t.tone ?? 'neutral' }) : nothing}
230
+ </div>`;
231
+ }
232
+ }
233
+ __decorate([
234
+ property({ attribute: false })
235
+ ], CaeTree.prototype, "items", void 0);
236
+ __decorate([
237
+ property({ attribute: false })
238
+ ], CaeTree.prototype, "selected", void 0);
239
+ __decorate([
240
+ property()
241
+ ], CaeTree.prototype, "label", void 0);
242
+ __decorate([
243
+ property({ attribute: false })
244
+ ], CaeTree.prototype, "strings", void 0);
245
+ __decorate([
246
+ state()
247
+ ], CaeTree.prototype, "opened", void 0);
248
+ __decorate([
249
+ state()
250
+ ], CaeTree.prototype, "focusId", void 0);
251
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","sourceRoot":"","sources":["../../src/tree/tree.ts"],"names":[],"mappings":"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"}
package/docs/code.md ADDED
@@ -0,0 +1,55 @@
1
+ # Code
2
+
3
+ `<cae-code>` shows a text as code: highlighted by its language, each line
4
+ numbered, some lines marked, with a switch between long lines wrapped and
5
+ scrolled sideways and a button that copies the whole text. Register it with
6
+ `@contraagent/elements/code/define`; the element, the tokenizer it is drawn with
7
+ and the languages it reads are in `@contraagent/elements/code`.
8
+
9
+ <!-- not on the site -->
10
+ Live on this topic's page of the documentation site (`pnpm run docs` in `web/`).
11
+
12
+ ::code{file="examples/code.ts"}
13
+
14
+ ## The text
15
+
16
+ `code` is the text and `language` its language: a name of `LANGUAGES`, another
17
+ name of one (`py`, `ts`, `sh`, `yml`) or a file name (`src/main.rs`); any other
18
+ is shown as plain text. `start` is the number of the first line (1), and
19
+ `highlight` marks lines in those numbers: a number for one line, a pair for the
20
+ lines from the first to the last. A marked line has a bar beside its number and
21
+ a tint. `path` names the file in the header; `label` names the lines to a screen
22
+ reader (default: the path).
23
+
24
+ A long text costs time to draw, so the element shows at most `maxLines` lines
25
+ (5,000) and `maxCharacters` characters (500,000), cut at the end of a line, and
26
+ says under them how many lines the text has (`strings.cut`). The copy button
27
+ copies the whole text all the same.
28
+
29
+ ## Highlighting
30
+
31
+ The highlighter is a small tokenizer of the package: each language is a few
32
+ regular expressions and its words, so it needs no library and no request. It
33
+ reads Python, JSON, JavaScript, TypeScript, C, C++, Java, Go, Rust, shell, TOML
34
+ (and INI), YAML, SQL, XML (and HTML and SVG) and diffs. It marks comments,
35
+ strings, numbers, keywords, constants, built-in names, the names a function or a
36
+ type is given where it is defined, and a language's own parts (keys, tags,
37
+ attributes, added and removed lines); it does not parse, so a rare construct may
38
+ take the colour of a simpler one. `tokenize(code, language)` returns the tokens,
39
+ `highlight(code, language)` escaped HTML; markdown's code blocks use the same
40
+ (`@contraagent/elements/markdown`). The colours are the theme's `--ca-code-*`
41
+ tokens ([Theming](theming.md)).
42
+
43
+ ::code{file="../src/code/highlight.ts" from="/** What a token is" to="/** The languages highlighted"}
44
+
45
+ ## Reading it
46
+
47
+ The lines are one tab stop that scrolls with the arrow keys. The numbers are
48
+ drawn beside the lines and are not part of a selection, so selected lines copy
49
+ without them. `wrap` wraps long lines at first; the reader's switch
50
+ (`strings.wrap`) changes it.
51
+
52
+ ## Look
53
+
54
+ `--cae-code-max-height` sets the most the lines grow to before they scroll (70vh).
55
+ The parts are `header`, `code` and `cut`.
@@ -0,0 +1,28 @@
1
+ // Code: a hand-written script, numbered from line 12, two of its lines marked; and
2
+ // the same element on a text too long to show whole.
3
+ import '@contraagent/elements/code/define';
4
+
5
+ const script = `def build(target: str, jobs: int = 4) -> bool:
6
+ """Build one target; True when it passed."""
7
+ result = run(["make", f"-j{jobs}", target])
8
+ if result.returncode != 0:
9
+ log.error("build of %s failed", target) # the log names the target
10
+ return False
11
+ return True
12
+ `;
13
+
14
+ export function mount(host: HTMLElement): void {
15
+ const code = document.createElement('cae-code');
16
+ code.code = script;
17
+ code.language = 'python';
18
+ code.path = 'ci/build.py';
19
+ code.start = 12;
20
+ code.highlight = [15, [16, 17]];
21
+
22
+ const long = document.createElement('cae-code');
23
+ long.code = Array.from({ length: 40 }, (_, i) => `step ${i + 1}: ok`).join('\n');
24
+ long.path = 'logs/steps.txt';
25
+ long.maxLines = 6;
26
+ long.style.marginTop = '12px';
27
+ host.replaceChildren(code, long);
28
+ }
@@ -0,0 +1,44 @@
1
+ // A tree of a CI pipeline's files, paired with a code element: selecting a file shows
2
+ // its text. The host holds the selection and answers `cae-select`.
3
+ import '@contraagent/elements/code/define';
4
+ import '@contraagent/elements/tree/define';
5
+ import type { TreeItem } from '@contraagent/elements/tree';
6
+
7
+ const files: Record<string, string> = {
8
+ 'ci/build.py': 'def build(target: str) -> bool:\n return run(["make", target]).returncode == 0\n',
9
+ 'ci/jobs.yaml': 'jobs:\n unit: { needs: [build] }\n lint: { needs: [] }\n',
10
+ 'ci/tests/test_build.py': 'def test_build():\n assert build("all")\n',
11
+ 'README.md': 'The CI pipeline of the project.\n',
12
+ };
13
+
14
+ const items: TreeItem[] = [
15
+ {
16
+ id: 'ci',
17
+ name: 'ci',
18
+ children: [
19
+ { id: 'ci/build.py', name: 'build.py' },
20
+ { id: 'ci/jobs.yaml', name: 'jobs.yaml', badge: 'changed', tone: 'attention' },
21
+ { id: 'ci/tests', name: 'tests', open: false, children: [{ id: 'ci/tests/test_build.py', name: 'test_build.py', badge: 'failed', tone: 'failed' }] },
22
+ ],
23
+ },
24
+ { id: 'README.md', name: 'README.md' },
25
+ ];
26
+
27
+ export function mount(host: HTMLElement): void {
28
+ const tree = document.createElement('cae-tree');
29
+ tree.items = items;
30
+ tree.label = 'Files';
31
+ const code = document.createElement('cae-code');
32
+ const show = (id: string) => {
33
+ tree.selected = id;
34
+ code.code = files[id] ?? '';
35
+ code.path = id;
36
+ code.language = id;
37
+ };
38
+ tree.addEventListener('cae-select', (e) => e.detail.id && show(e.detail.id));
39
+ show('ci/build.py');
40
+ const pair = document.createElement('div');
41
+ pair.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; align-items: start';
42
+ pair.append(tree, code);
43
+ host.replaceChildren(pair);
44
+ }
package/docs/tree.md ADDED
@@ -0,0 +1,44 @@
1
+ # Tree
2
+
3
+ `<cae-tree>` shows items in a tree, the children of each under it, folded and
4
+ opened by the reader: files and directories, say. Register it with
5
+ `@contraagent/elements/tree/define`; the types are in
6
+ `@contraagent/elements/tree`.
7
+
8
+ <!-- not on the site -->
9
+ Live on this topic's page of the documentation site (`pnpm run docs` in `web/`).
10
+
11
+ ::code{file="examples/tree.ts"}
12
+
13
+ ## Items
14
+
15
+ An item is a `TreeItem`: an `id` unique in the tree, a `name` and its
16
+ `children`. Its icon is a directory for an item with children and a file for one
17
+ without, unless `icon` names one. A `badge` is a word beside the name in the
18
+ colour of its `tone`, with the tone's mark (✓ done, ✕ failed, ◌ attention).
19
+ `open: false` folds an item at first.
20
+
21
+ ::code{file="../src/tree/tree.ts" from="/** One item of a tree" to="export interface TreeStrings"}
22
+
23
+ ## Selection
24
+
25
+ `selected` is the selected item's id; the element shows it and opens its
26
+ ancestors, and the host sets it. A click, Enter or Space on a selectable item
27
+ sends `cae-select` with its id: by default an item with no children is
28
+ selectable, and an item with children opens and closes instead. The toggle
29
+ before an item with children, Right and Left open and close it and send
30
+ `cae-toggle`. A host pairs a tree with `<cae-code>` by showing the selected
31
+ file's text beside it ([Code](code.md)).
32
+
33
+ ## Keyboard
34
+
35
+ The tree is one tab stop, on the selected item or else the first. Up and Down
36
+ move between the items shown, Right opens an item and then moves to its first
37
+ child, Left closes it or moves to its parent, Home and End go to the first and
38
+ the last item, and a letter moves to the next item whose name starts with it. A
39
+ screen reader hears each item's level, its place among its siblings, and whether
40
+ it is open and selected.
41
+
42
+ ## Look
43
+
44
+ The parts are `tree`, `item` (with `data-id`) and `empty`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@contraagent/elements",
3
- "version": "0.1.0",
4
- "description": "Generic web components (Lit) and their parts: design tokens, themes, status pills, legends, a segmented control, a data table, a layered graph, a mark matrix, stat tiles, time lanes, charts (columns, lines, a heatmap, horizontal bars), a check table, a transcript, a JSON tree, markdown and counters. No contraagent data, no localization library",
3
+ "version": "0.2.0",
4
+ "description": "Generic web components (Lit) and their parts: design tokens, themes, status pills, legends, a segmented control, a data table, a layered graph, a mark matrix, stat tiles, time lanes, charts (columns, lines, a heatmap, horizontal bars), a check table, a transcript, a JSON tree, markdown, highlighted code, a tree of items and counters. No contraagent data, no localization library",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "web-components",
@@ -46,6 +46,14 @@
46
46
  "types": "./dist/markdown/index.d.ts",
47
47
  "default": "./dist/markdown/index.js"
48
48
  },
49
+ "./code": {
50
+ "types": "./dist/code/index.d.ts",
51
+ "default": "./dist/code/index.js"
52
+ },
53
+ "./tree": {
54
+ "types": "./dist/tree/index.d.ts",
55
+ "default": "./dist/tree/index.js"
56
+ },
49
57
  "./count": {
50
58
  "types": "./dist/count/index.d.ts",
51
59
  "default": "./dist/count/index.js"
@@ -94,6 +102,14 @@
94
102
  "types": "./dist/define/markdown.d.ts",
95
103
  "default": "./dist/define/markdown.js"
96
104
  },
105
+ "./code/define": {
106
+ "types": "./dist/define/code.d.ts",
107
+ "default": "./dist/define/code.js"
108
+ },
109
+ "./tree/define": {
110
+ "types": "./dist/define/tree.d.ts",
111
+ "default": "./dist/define/tree.js"
112
+ },
97
113
  "./count/define": {
98
114
  "types": "./dist/define/count.d.ts",
99
115
  "default": "./dist/define/count.js"
@@ -157,7 +173,7 @@
157
173
  "lit": "^3.3.3"
158
174
  },
159
175
  "scripts": {
160
- "build": "tsc -p tsconfig.build.json && cem analyze --litelement --globs 'src/*/{segmented,data-table,layered-graph,mark-matrix,stat-tiles,lanes,column-chart,line-chart,heatmap,bar-chart,check-table,transcript,json-tree,cae-markdown,count,elapsed}.ts'",
176
+ "build": "tsc -p tsconfig.build.json && cem analyze --litelement --globs 'src/*/{segmented,data-table,layered-graph,mark-matrix,stat-tiles,lanes,column-chart,line-chart,heatmap,bar-chart,check-table,transcript,json-tree,cae-markdown,code,tree,count,elapsed}.ts'",
161
177
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p docs"
162
178
  }
163
179
  }