@astronox/core 0.2.0 → 0.2.2

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 (81) hide show
  1. package/README.md +9 -30
  2. package/package.json +7 -1
  3. package/src/admin/contracts.ts +28 -0
  4. package/src/admin/index.ts +2 -0
  5. package/src/admin/module.ts +28 -0
  6. package/src/auth/contracts.ts +53 -0
  7. package/src/auth/firebase/client.ts +53 -0
  8. package/src/auth/firebase/contracts.ts +8 -0
  9. package/src/auth/firebase/index.ts +1 -0
  10. package/src/auth/index.ts +2 -0
  11. package/src/auth/module.ts +99 -0
  12. package/src/client/index.ts +3 -0
  13. package/src/client/preferences.ts +400 -0
  14. package/src/components/actions/Button/Button.astro +42 -6
  15. package/src/components/actions/Button/Button.css +19 -0
  16. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +35 -0
  17. package/src/components/actions/MediaTrigger/MediaTrigger.astro +12 -4
  18. package/src/components/actions/MediaTrigger/MediaTrigger.css +1 -0
  19. package/src/components/content/CardBody/CardBody.astro +3 -0
  20. package/src/components/content/CardBody/CardBody.css +10 -0
  21. package/src/components/content/MarkdownContent/MarkdownContent.css +0 -1
  22. package/src/components/content/MarkdownContent/MarkdownNode.astro +1 -0
  23. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +549 -0
  24. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +403 -0
  25. package/src/components/forms/Checkbox/Checkbox.astro +66 -0
  26. package/src/components/forms/Checkbox/Checkbox.css +126 -0
  27. package/src/components/forms/ChoiceGroup/ChoiceGroup.astro +57 -0
  28. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +108 -0
  29. package/src/components/forms/ChoiceGroup/contracts.ts +9 -0
  30. package/src/components/forms/DateTimeInput/DateTimeInput.astro +14 -0
  31. package/src/components/forms/FieldGroup/FieldGroup.astro +34 -0
  32. package/src/components/forms/FieldGroup/FieldGroup.css +30 -0
  33. package/src/components/forms/TextInput/TextInput.astro +7 -1
  34. package/src/components/forms/Textarea/Textarea.astro +32 -0
  35. package/src/components/forms/Textarea/Textarea.css +54 -0
  36. package/src/components/icons/BackIcon.astro +26 -0
  37. package/src/components/icons/CheckIcon.astro +22 -0
  38. package/src/components/icons/DeleteIcon.astro +22 -0
  39. package/src/components/icons/StatusIndicatorIcon.astro +16 -0
  40. package/src/components/index.ts +12 -0
  41. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -17
  42. package/src/components/media/EmbedFrame/EmbedFrame.astro +1 -19
  43. package/src/components/media/EmbedFrame/EmbedFrame.css +0 -2
  44. package/src/components/navigation/SubNavigation/SubNavigation.css +0 -5
  45. package/src/composition/Grid/Grid.astro +9 -0
  46. package/src/composition/Grid/Grid.css +52 -0
  47. package/src/composition/GridItem/GridItem.astro +9 -0
  48. package/src/composition/GridItem/GridItem.css +12 -0
  49. package/src/composition/SurfaceGroup/SurfaceGroup.css +21 -7
  50. package/src/composition/SurfaceGroup/stickySurfaceGroupController.ts +3 -0
  51. package/src/data/firestore/client/contracts.ts +55 -0
  52. package/src/data/firestore/client/firebase.ts +12 -0
  53. package/src/data/firestore/client/index.ts +3 -0
  54. package/src/data/firestore/client/repository.ts +111 -0
  55. package/src/display/contracts.ts +41 -0
  56. package/src/display/index.ts +1 -0
  57. package/src/display/tableProgressive.ts +62 -0
  58. package/src/patterns/Combobox/Combobox.astro +23 -0
  59. package/src/patterns/Combobox/comboboxController.ts +72 -0
  60. package/src/patterns/Fullscreen/FullscreenControl.astro +4 -14
  61. package/src/patterns/Fullscreen/FullscreenControl.css +0 -26
  62. package/src/patterns/Modal/Modal.astro +1 -1
  63. package/src/patterns/Modal/Modal.css +38 -1
  64. package/src/patterns/Table/Table.astro +122 -7
  65. package/src/patterns/Table/Table.css +2 -1
  66. package/src/patterns/Table/TableBody.astro +11 -1
  67. package/src/patterns/Table/TableBody.css +6 -0
  68. package/src/patterns/Table/TableCell.css +6 -0
  69. package/src/patterns/Table/TableCellValue.astro +31 -0
  70. package/src/patterns/Table/TableFooter.astro +3 -1
  71. package/src/patterns/Table/TableHeader.astro +1 -0
  72. package/src/patterns/Table/TablePagination.astro +63 -47
  73. package/src/patterns/Table/TableSkeleton.astro +18 -22
  74. package/src/patterns/Table/TableSkeleton.css +30 -5
  75. package/src/patterns/Table/tableClientController.ts +253 -0
  76. package/src/patterns/Table/tableFilterController.ts +83 -33
  77. package/src/patterns/Table/tableLayout.ts +10 -3
  78. package/src/patterns/Table/tableProgressiveController.ts +615 -227
  79. package/src/patterns/Table/tableSortController.ts +25 -7
  80. package/src/styles/interaction.css +16 -0
  81. package/src/styles/style.css +4 -0
@@ -0,0 +1,549 @@
1
+ import type { DisplayMedia } from '../../../display/contracts';
2
+ import type { Tone } from '../../../theme/contracts';
3
+ import { initCodeBlock } from '../CodeBlock/codeBlockController';
4
+ import type { CodeBlockCopy } from '../CodeBlock/contracts';
5
+ import type {
6
+ MarkdownBlock,
7
+ MarkdownDocument,
8
+ MarkdownInline,
9
+ MarkdownListItem,
10
+ MarkdownListMarker,
11
+ MarkdownSection,
12
+ MarkdownSymbol,
13
+ } from './contracts';
14
+
15
+ type RenderOptions = {
16
+ codeBlockCopy?: CodeBlockCopy;
17
+ };
18
+
19
+ const svgNamespace = 'http://www.w3.org/2000/svg';
20
+ const codeBlockTones = new Set<Tone>([
21
+ 'neutral',
22
+ 'info',
23
+ 'success',
24
+ 'warning',
25
+ 'danger',
26
+ ]);
27
+ const defaultListMarker = {
28
+ symbol: 'bullet',
29
+ tone: 'neutral',
30
+ } as const satisfies MarkdownListMarker;
31
+ const nestedListMarker = {
32
+ symbol: 'arrow-right',
33
+ tone: 'neutral',
34
+ } as const satisfies MarkdownListMarker;
35
+
36
+ export function renderMarkdownDocument(
37
+ root: HTMLElement,
38
+ document: MarkdownDocument,
39
+ options: RenderOptions = {},
40
+ ): void {
41
+ const fragment = window.document.createDocumentFragment();
42
+ document.blocks.forEach((block) => {
43
+ fragment.append(renderNode(block, options));
44
+ });
45
+ root.replaceChildren(fragment);
46
+ }
47
+
48
+ function renderNode(
49
+ node: MarkdownBlock | MarkdownSection,
50
+ options: RenderOptions,
51
+ ): HTMLElement {
52
+ if (node.type === 'section') {
53
+ const section = createElement('section', 'markdown-content__section');
54
+ section.setAttribute('aria-labelledby', node.id);
55
+ const heading = createHeading(
56
+ node.level,
57
+ 'markdown-content__section-title',
58
+ );
59
+ heading.id = node.id;
60
+ heading.textContent = node.title;
61
+ const body = createElement('div', 'markdown-content__section-body');
62
+ node.blocks.forEach((block) => body.append(renderNode(block, options)));
63
+ section.append(heading, body);
64
+ return section;
65
+ }
66
+
67
+ if (node.type === 'paragraph') {
68
+ const paragraph = createElement('p', 'markdown-content__paragraph');
69
+ appendInline(paragraph, node.children);
70
+ return paragraph;
71
+ }
72
+
73
+ if (node.type === 'heading') {
74
+ const heading = createHeading(node.level, 'markdown-content__heading');
75
+ heading.id = node.id;
76
+ appendInline(heading, node.children);
77
+ return heading;
78
+ }
79
+
80
+ if (node.type === 'change') {
81
+ const change = createElement('div', 'markdown-content__change');
82
+ const label = createElement('span', 'markdown-content__change-label');
83
+ appendInline(label, node.label);
84
+ label.append(':');
85
+ const values = createElement('span', 'markdown-content__change-values');
86
+
87
+ if (node.presentation === 'statement') {
88
+ appendInline(values, node.value);
89
+ } else {
90
+ if (node.valueLang) values.lang = node.valueLang;
91
+ const transition = createElement(
92
+ 'span',
93
+ 'markdown-content__change-transition',
94
+ );
95
+ transition.append(
96
+ document.createTextNode(node.before),
97
+ renderSymbol({
98
+ type: 'symbol',
99
+ name: 'arrow-right',
100
+ accessibleLabel: node.transitionLabel,
101
+ lang: node.valueLang,
102
+ }),
103
+ document.createTextNode(node.after),
104
+ );
105
+ if (node.delta) {
106
+ const delta = createElement(
107
+ 'span',
108
+ 'markdown-content__change-delta',
109
+ );
110
+ delta.dataset.tone = node.delta.tone;
111
+ delta.append('(');
112
+ if (node.delta.sign !== 'neutral') {
113
+ const accessible = createElement(
114
+ 'span',
115
+ 'markdown-content__visually-hidden',
116
+ );
117
+ accessible.textContent = node.delta.accessibleLabel;
118
+ const sign = document.createElement('span');
119
+ sign.ariaHidden = 'true';
120
+ sign.textContent = node.delta.sign === 'plus' ? '+' : '-';
121
+ delta.append(accessible, sign);
122
+ }
123
+ delta.append(node.delta.value, ')');
124
+ transition.append(delta);
125
+ }
126
+ values.append(transition);
127
+ if (node.suffix?.length) {
128
+ const suffix = createElement(
129
+ 'span',
130
+ 'markdown-content__change-suffix',
131
+ );
132
+ appendInline(suffix, node.suffix);
133
+ values.append(suffix);
134
+ }
135
+ }
136
+
137
+ change.append(label, values);
138
+ return change;
139
+ }
140
+
141
+ if (node.type === 'list') {
142
+ const list = createElement('ul', 'markdown-content__list');
143
+ if (node.items.some((item) => item.heading)) {
144
+ list.dataset.markdownHasHeadings = 'true';
145
+ }
146
+ if (node.items.some((item) => item.resource)) {
147
+ list.dataset.markdownHasResources = 'true';
148
+ }
149
+ node.items.forEach((item) => list.append(renderListItem(item, options)));
150
+ return list;
151
+ }
152
+
153
+ if (node.type === 'quote') {
154
+ const quote = createElement('blockquote', 'markdown-content__quote');
155
+ quote.dataset.tone = node.tone ?? 'neutral';
156
+ node.children.forEach((block) => quote.append(renderNode(block, options)));
157
+ return quote;
158
+ }
159
+
160
+ if (node.type === 'separator') {
161
+ const separator = createElement('hr', 'separator');
162
+ separator.dataset.widget = 'separator';
163
+ separator.dataset.separatorBleed = 'true';
164
+ separator.dataset.separatorEmphasis = node.emphasis ?? 'muted';
165
+ return separator;
166
+ }
167
+
168
+ return renderCodeBlock(node.language, node.value, options.codeBlockCopy);
169
+ }
170
+
171
+ function renderListItem(
172
+ item: MarkdownListItem,
173
+ options: RenderOptions,
174
+ ): HTMLLIElement {
175
+ const listItem = createElement('li', 'markdown-content__list-item');
176
+ const marker =
177
+ item.marker ??
178
+ (item.heading
179
+ ? nestedListMarker
180
+ : item.resource
181
+ ? undefined
182
+ : defaultListMarker);
183
+
184
+ if (marker) listItem.dataset.markdownHasMarker = 'true';
185
+ if (item.resource) {
186
+ listItem.dataset.markdownResource = 'true';
187
+ listItem.dataset.markdownResourceLayout = item.resource.layout;
188
+ listItem.dataset.markdownResourceMedia = item.resource.mediaPlacement;
189
+ listItem.dataset.markdownResourceMediaScale = item.resource.mediaScale;
190
+ }
191
+ if (item.resource?.mediaPlacement === 'lead') {
192
+ listItem.append(
193
+ renderMedia(
194
+ item.resource.media,
195
+ 'markdown-content__lead-media',
196
+ 'fill',
197
+ item.resource.mediaScale === 'compact',
198
+ ),
199
+ );
200
+ }
201
+ if (marker) {
202
+ listItem.append(
203
+ renderSymbol({
204
+ type: 'symbol',
205
+ name: marker.symbol,
206
+ accessibleLabel: marker.label,
207
+ lang: marker.lang,
208
+ tone: marker.tone,
209
+ }),
210
+ );
211
+ }
212
+
213
+ const body = createElement('div', 'markdown-content__list-item-body');
214
+ if (item.heading) {
215
+ const heading = createHeading(5, 'markdown-content__heading');
216
+ appendInline(heading, item.heading);
217
+ body.append(heading);
218
+ } else if (item.resource) {
219
+ const heading = createElement(
220
+ 'div',
221
+ 'markdown-content__resource-heading',
222
+ );
223
+ if (item.resource.mediaPlacement === 'inline') {
224
+ heading.append(
225
+ renderMedia(
226
+ item.resource.media,
227
+ 'markdown-content__inline-media',
228
+ 'intrinsic',
229
+ item.resource.mediaScale === 'compact',
230
+ ),
231
+ );
232
+ }
233
+ const title = createElement(
234
+ item.resource.layout === 'detailed' ? 'h3' : 'span',
235
+ 'markdown-content__resource-title',
236
+ );
237
+ appendInline(title, item.resource.title);
238
+ heading.append(title);
239
+ body.append(heading);
240
+ }
241
+ item.children.forEach((block) => body.append(renderNode(block, options)));
242
+ listItem.append(body);
243
+ return listItem;
244
+ }
245
+
246
+ function appendInline(
247
+ parent: HTMLElement,
248
+ nodes: readonly MarkdownInline[],
249
+ ): void {
250
+ nodes.forEach((node) => parent.append(renderInline(node)));
251
+ }
252
+
253
+ function renderInline(node: MarkdownInline): Node {
254
+ if (node.type === 'text') {
255
+ if (!node.lang) return document.createTextNode(node.value);
256
+ const text = document.createElement('span');
257
+ text.lang = node.lang;
258
+ text.textContent = node.value;
259
+ return text;
260
+ }
261
+ if (node.type === 'symbol') return renderSymbol(node);
262
+ if (node.type === 'strong' || node.type === 'emphasis') {
263
+ const element = document.createElement(
264
+ node.type === 'strong' ? 'strong' : 'em',
265
+ );
266
+ appendInline(element, node.children);
267
+ return element;
268
+ }
269
+ if (node.type === 'code') {
270
+ const code = createElement('code', 'markdown-content__inline-code');
271
+ code.textContent = node.value;
272
+ return code;
273
+ }
274
+ if (node.type === 'link') {
275
+ const link = createElement('a', 'markdown-content__link');
276
+ link.href = node.href;
277
+ link.dataset.tone = 'info';
278
+ if (node.lang) link.lang = node.lang;
279
+ if (node.treatment) link.dataset.markdownLinkTreatment = node.treatment;
280
+ if (node.external) {
281
+ link.target = '_blank';
282
+ link.rel = 'noopener noreferrer';
283
+ }
284
+ appendInline(link, node.children);
285
+ return link;
286
+ }
287
+ return document.createElement('br');
288
+ }
289
+
290
+ function renderSymbol(node: MarkdownSymbol): HTMLSpanElement {
291
+ const symbol = createElement('span', 'markdown-content__symbol');
292
+ symbol.dataset.markdownSymbol = node.name;
293
+ if (node.tone) symbol.dataset.tone = node.tone;
294
+ if (node.lang) symbol.lang = node.lang;
295
+ symbol.append(createSymbolIcon(node.name));
296
+
297
+ if (node.label) {
298
+ const label = createElement('span', 'markdown-content__symbol-label');
299
+ label.textContent = node.label;
300
+ symbol.append(label);
301
+ } else if (node.accessibleLabel) {
302
+ const label = createElement(
303
+ 'span',
304
+ 'markdown-content__visually-hidden',
305
+ );
306
+ label.textContent = node.accessibleLabel;
307
+ symbol.append(label);
308
+ }
309
+ return symbol;
310
+ }
311
+
312
+ function createSymbolIcon(name: MarkdownSymbol['name']): SVGSVGElement {
313
+ const svg = document.createElementNS(svgNamespace, 'svg');
314
+ svg.setAttribute('viewBox', '0 0 32 32');
315
+ svg.setAttribute('fill', 'none');
316
+ svg.setAttribute('aria-hidden', 'true');
317
+ svg.setAttribute('focusable', 'false');
318
+ svg.classList.add('markdown-content__symbol-icon');
319
+
320
+ if (name.startsWith('terrain-')) {
321
+ svg.classList.add('circle-symbol-icon');
322
+ svg.dataset.terrainIcon = name.slice('terrain-'.length);
323
+ appendTerrainIcon(svg, name);
324
+ return svg;
325
+ }
326
+
327
+ if (name === 'bullet') {
328
+ const circle = document.createElementNS(svgNamespace, 'circle');
329
+ circle.setAttribute('cx', '16');
330
+ circle.setAttribute('cy', '16');
331
+ circle.setAttribute('r', '4.5');
332
+ circle.setAttribute('fill', 'currentColor');
333
+ svg.append(circle);
334
+ return svg;
335
+ }
336
+
337
+ const path = document.createElementNS(svgNamespace, 'path');
338
+ path.setAttribute('stroke', 'currentColor');
339
+ path.setAttribute('stroke-linecap', 'round');
340
+ path.setAttribute('stroke-linejoin', 'round');
341
+ path.setAttribute('stroke-width', name === 'neutral' ? '3' : '2.4');
342
+ path.setAttribute(
343
+ 'd',
344
+ name === 'increase'
345
+ ? 'm7.5 19.5 8.5-8.5 8.5 8.5'
346
+ : name === 'decrease'
347
+ ? 'm7.5 12.5 8.5 8.5 8.5-8.5'
348
+ : name === 'adjustment'
349
+ ? 'M11 24V8m0 0-4 4m4-4 4 4m6-4v16m0 0-4-4m4 4 4-4'
350
+ : name === 'neutral'
351
+ ? 'M6 11.5h20M6 20.5h20'
352
+ : 'm12.5 7.5 8.5 8.5-8.5 8.5',
353
+ );
354
+ svg.append(path);
355
+ return svg;
356
+ }
357
+
358
+ function appendTerrainIcon(
359
+ svg: SVGSVGElement,
360
+ name: Extract<MarkdownSymbol['name'], `terrain-${string}`>,
361
+ ): void {
362
+ const surface = document.createElementNS(svgNamespace, 'circle');
363
+ surface.setAttribute('cx', '16');
364
+ surface.setAttribute('cy', '16');
365
+ surface.setAttribute('r', '13');
366
+ surface.classList.add('circle-symbol-icon__surface');
367
+ svg.append(surface);
368
+
369
+ if (name === 'terrain-field') {
370
+ const rectangle = document.createElementNS(svgNamespace, 'rect');
371
+ rectangle.setAttribute('x', '9');
372
+ rectangle.setAttribute('y', '9');
373
+ rectangle.setAttribute('width', '14');
374
+ rectangle.setAttribute('height', '14');
375
+ rectangle.setAttribute('rx', '1');
376
+ rectangle.classList.add('circle-symbol-icon__mark');
377
+ const grid = document.createElementNS(svgNamespace, 'path');
378
+ grid.setAttribute('d', 'M13.7 9v14m4.6-14v14M9 13.7h14M9 18.3h14');
379
+ grid.classList.add('circle-symbol-icon__mark');
380
+ svg.append(rectangle, grid);
381
+ return;
382
+ }
383
+
384
+ const fill = document.createElementNS(svgNamespace, 'path');
385
+ fill.classList.add('circle-symbol-icon__fill');
386
+ fill.setAttribute(
387
+ 'd',
388
+ name === 'terrain-grass'
389
+ ? 'M23.5 8.5c.2 8.4-3.7 14-11.7 15.2-3.1-6.8.2-13.5 11.7-15.2Z'
390
+ : name === 'terrain-water'
391
+ ? 'M16 5.5c3.1 4.2 7.5 9 7.5 13.3A7.5 7.5 0 1 1 8.5 18.8C8.5 14.5 12.9 9.7 16 5.5Z'
392
+ : 'm5.5 24 7.3-13 3.1 5.1 3.4-7.1 7.2 15H5.5Z',
393
+ );
394
+ const highlight = document.createElementNS(svgNamespace, 'path');
395
+ highlight.classList.add('circle-symbol-icon__highlight');
396
+ highlight.setAttribute(
397
+ 'd',
398
+ name === 'terrain-grass'
399
+ ? 'M10.4 23.5c2.2-5.3 5.4-8.7 10.2-11.6'
400
+ : name === 'terrain-water'
401
+ ? 'M12.2 19.2a4.5 4.5 0 0 0 4.5 4.1'
402
+ : 'm10.8 14.6 2-3.6 3.1 5.1 1.4-2.9 2.4 3.6-3.5-1.1-2.5 2.1-2.9-3.2Z',
403
+ );
404
+ svg.append(fill, highlight);
405
+ }
406
+
407
+ function renderMedia(
408
+ media: DisplayMedia,
409
+ className: string,
410
+ size: 'fill' | 'intrinsic',
411
+ preventUpscale: boolean,
412
+ ): HTMLElement {
413
+ const figure = createElement('figure', `media ${className}`);
414
+ figure.dataset.widget = 'media';
415
+ figure.dataset.mediaFit = media.fit ?? 'contain';
416
+ figure.dataset.mediaFraming = media.framing ?? 'asset';
417
+ figure.dataset.mediaRendering = media.rendering ?? 'auto';
418
+ figure.dataset.mediaShape = media.shape ?? 'default';
419
+ figure.dataset.mediaSize = size;
420
+ figure.dataset.mediaUpscale = preventUpscale
421
+ ? 'prevent'
422
+ : (media.upscale ?? 'allow');
423
+ figure.dataset.mediaVisualSize = media.visualSize ?? 'default';
424
+ figure.style.setProperty(
425
+ '--media-aspect-ratio',
426
+ `${media.width} / ${media.height}`,
427
+ );
428
+ figure.style.setProperty('--media-source-inline-size', `${media.width}px`);
429
+ figure.style.setProperty('--media-source-block-size', `${media.height}px`);
430
+
431
+ const frame = createElement('div', 'media__frame');
432
+ const image = document.createElement('img');
433
+ image.src = media.src;
434
+ image.alt = media.alt;
435
+ image.width = media.width;
436
+ image.height = media.height;
437
+ image.loading = media.loading ?? 'lazy';
438
+ image.decoding = media.decoding ?? 'async';
439
+ if (media.sources?.length) {
440
+ image.srcset = media.sources
441
+ .map((source) => `${source.src} ${source.width}w`)
442
+ .join(', ');
443
+ }
444
+ if (media.sizes) image.sizes = media.sizes;
445
+ frame.append(image);
446
+ figure.append(frame);
447
+ if (media.caption) {
448
+ const caption = document.createElement('figcaption');
449
+ caption.textContent = media.caption;
450
+ figure.append(caption);
451
+ }
452
+ return figure;
453
+ }
454
+
455
+ function renderCodeBlock(
456
+ language: string | undefined,
457
+ value: string,
458
+ copy: CodeBlockCopy | undefined,
459
+ ): HTMLElement {
460
+ const normalized = language?.trim().toLowerCase() || undefined;
461
+ const presentation = normalized === 'markdown' ? 'light' : 'default';
462
+ const tone =
463
+ normalized && codeBlockTones.has(normalized as Tone)
464
+ ? (normalized as Tone)
465
+ : 'neutral';
466
+ const content = createElement('pre', 'code-block');
467
+ content.dataset.codeBlockPresentation = presentation;
468
+ content.dataset.wrap = 'false';
469
+ content.textContent = value;
470
+ if (!copy) {
471
+ content.dataset.widget = 'code-block';
472
+ content.dataset.tone = tone;
473
+ return content;
474
+ }
475
+
476
+ const shell = createElement('nox-code-block', 'code-block-shell');
477
+ shell.dataset.widget = 'code-block';
478
+ shell.dataset.tone = tone;
479
+ content.dataset.codeBlockContent = '';
480
+ const button = createElement('button', 'nox-button code-block__copy');
481
+ button.type = 'button';
482
+ button.dataset.buttonBorderless = 'true';
483
+ button.dataset.buttonHoverTone = 'info';
484
+ button.dataset.buttonIconOnly = 'true';
485
+ button.dataset.buttonSize = 'sm';
486
+ button.dataset.codeBlockCopy = '';
487
+ button.dataset.copyLabel = copy.copyLabel;
488
+ button.dataset.copiedLabel = copy.copiedLabel;
489
+ button.dataset.copyUnavailableLabel = copy.unavailableLabel;
490
+ button.setAttribute('aria-label', copy.copyLabel);
491
+ button.title = copy.copyLabel;
492
+ if (copy.lang) button.lang = copy.lang;
493
+ button.append(createCopyIcon());
494
+ const status = createElement('span', 'code-block__status');
495
+ status.dataset.codeBlockStatus = '';
496
+ status.setAttribute('aria-live', 'polite');
497
+ if (copy.lang) status.lang = copy.lang;
498
+ shell.append(content, button, status);
499
+ initCodeBlock(shell);
500
+ return shell;
501
+ }
502
+
503
+ function createCopyIcon(): SVGSVGElement {
504
+ const svg = document.createElementNS(svgNamespace, 'svg');
505
+ svg.setAttribute('viewBox', '0 0 32 32');
506
+ svg.setAttribute('fill', 'none');
507
+ svg.setAttribute('aria-hidden', 'true');
508
+ svg.setAttribute('focusable', 'false');
509
+ const rectangle = document.createElementNS(svgNamespace, 'rect');
510
+ rectangle.setAttribute('x', '11');
511
+ rectangle.setAttribute('y', '11');
512
+ rectangle.setAttribute('width', '14');
513
+ rectangle.setAttribute('height', '14');
514
+ rectangle.setAttribute('rx', '2');
515
+ rectangle.setAttribute('stroke', 'currentColor');
516
+ rectangle.setAttribute('stroke-width', '2.5');
517
+ const path = document.createElementNS(svgNamespace, 'path');
518
+ path.setAttribute(
519
+ 'd',
520
+ 'M21 11V9C21 7.89543 20.1046 7 19 7H9C7.89543 7 7 7.89543 7 9V19C7 20.1046 7.89543 21 9 21H11',
521
+ );
522
+ path.setAttribute('stroke', 'currentColor');
523
+ path.setAttribute('stroke-width', '2.5');
524
+ path.setAttribute('stroke-linecap', 'round');
525
+ path.setAttribute('stroke-linejoin', 'round');
526
+ svg.append(rectangle, path);
527
+ return svg;
528
+ }
529
+
530
+ function createHeading(
531
+ level: number,
532
+ className: string,
533
+ ): HTMLHeadingElement {
534
+ const heading = createElement(
535
+ `h${Math.min(6, Math.max(2, level))}` as keyof HTMLElementTagNameMap,
536
+ className,
537
+ ) as HTMLHeadingElement;
538
+ heading.dataset.markdownHeadingLevel = String(level);
539
+ return heading;
540
+ }
541
+
542
+ function createElement<K extends keyof HTMLElementTagNameMap>(
543
+ tag: K,
544
+ className?: string,
545
+ ): HTMLElementTagNameMap[K] {
546
+ const element = document.createElement(tag);
547
+ if (className) element.className = className;
548
+ return element;
549
+ }