@echodial/deck 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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1327 -0
  3. package/bin/deck.mjs +219 -0
  4. package/build.mjs +338 -0
  5. package/dist/api-buckets.json +10486 -0
  6. package/dist/api.json +57896 -0
  7. package/dist/brand/deck-apple-touch-icon.png +0 -0
  8. package/dist/brand/deck-logo-dark.svg +1 -0
  9. package/dist/brand/deck-logo-light.svg +1 -0
  10. package/dist/brand/deck-logo-stacked.svg +1 -0
  11. package/dist/brand/deck-logo.svg +1 -0
  12. package/dist/brand/deck-mark.svg +7 -0
  13. package/dist/brand/deck-og.png +0 -0
  14. package/dist/collisions.json +223 -0
  15. package/dist/deck-adapters.js +482 -0
  16. package/dist/deck-adapters.min.js +23 -0
  17. package/dist/deck-extras.js +823 -0
  18. package/dist/deck-extras.min.js +5 -0
  19. package/dist/deck-icons.svg +184 -0
  20. package/dist/deck.bundle.js +2638 -0
  21. package/dist/deck.bundle.min.js +86 -0
  22. package/dist/deck.css +7904 -0
  23. package/dist/deck.esm.js +2644 -0
  24. package/dist/deck.js +1331 -0
  25. package/dist/deck.min.css +32 -0
  26. package/dist/deck.min.js +60 -0
  27. package/dist/layers/anchor.css +192 -0
  28. package/dist/layers/buttons.css +210 -0
  29. package/dist/layers/charts.css +337 -0
  30. package/dist/layers/combobox.css +248 -0
  31. package/dist/layers/components.css +868 -0
  32. package/dist/layers/container.css +199 -0
  33. package/dist/layers/datagrid.css +335 -0
  34. package/dist/layers/datepicker.css +305 -0
  35. package/dist/layers/forms.css +401 -0
  36. package/dist/layers/gradients.css +302 -0
  37. package/dist/layers/inputs.css +522 -0
  38. package/dist/layers/layers.css +87 -0
  39. package/dist/layers/layout.css +235 -0
  40. package/dist/layers/logical.css +226 -0
  41. package/dist/layers/media.css +400 -0
  42. package/dist/layers/mobile.css +245 -0
  43. package/dist/layers/motion.css +379 -0
  44. package/dist/layers/nav.css +771 -0
  45. package/dist/layers/perf.css +234 -0
  46. package/dist/layers/print.css +198 -0
  47. package/dist/layers/reset.css +103 -0
  48. package/dist/layers/space3d.css +280 -0
  49. package/dist/layers/toasts.css +201 -0
  50. package/dist/layers/tokens.css +229 -0
  51. package/dist/layers/type.css +114 -0
  52. package/dist/layers/utilities.css +241 -0
  53. package/dist/sizes.json +52 -0
  54. package/dist/usage.json +6340 -0
  55. package/package.json +94 -0
  56. package/src/00-layers.css +87 -0
  57. package/src/01-tokens.css +229 -0
  58. package/src/02-reset.css +103 -0
  59. package/src/03-type.css +114 -0
  60. package/src/04-layout.css +235 -0
  61. package/src/05-buttons.css +210 -0
  62. package/src/06-forms.css +401 -0
  63. package/src/07-components.css +868 -0
  64. package/src/08-mobile.css +245 -0
  65. package/src/09-utilities.css +241 -0
  66. package/src/10-datepicker.css +305 -0
  67. package/src/11-combobox.css +248 -0
  68. package/src/12-datagrid.css +335 -0
  69. package/src/13-toasts.css +201 -0
  70. package/src/14-charts.css +337 -0
  71. package/src/16-motion.css +379 -0
  72. package/src/18-container.css +199 -0
  73. package/src/19-logical.css +226 -0
  74. package/src/20-gradients.css +302 -0
  75. package/src/21-space3d.css +280 -0
  76. package/src/22-nav.css +771 -0
  77. package/src/23-inputs.css +522 -0
  78. package/src/24-media.css +400 -0
  79. package/src/25-anchor.css +192 -0
  80. package/src/26-perf.css +234 -0
  81. package/src/99-print.css +198 -0
  82. package/src/brand/deck-apple-touch-icon.png +0 -0
  83. package/src/brand/deck-logo-dark.svg +1 -0
  84. package/src/brand/deck-logo-light.svg +1 -0
  85. package/src/brand/deck-logo-stacked.svg +1 -0
  86. package/src/brand/deck-logo.svg +1 -0
  87. package/src/brand/deck-mark.svg +7 -0
  88. package/src/brand/deck-og.png +0 -0
  89. package/src/brand/sources.json +7 -0
  90. package/src/deck-icons.svg +184 -0
  91. package/src/js/deck-adapters.js +482 -0
  92. package/src/js/deck-extras.js +823 -0
  93. package/src/js/deck.js +1331 -0
@@ -0,0 +1,482 @@
1
+ /*! ==========================================================================
2
+ * deck-adapters.js v0.1
3
+ *
4
+ * Deck's core is zero-dependency on purpose. This file is the opposite: it
5
+ * is a set of adapters that light up ONLY if the corresponding library is
6
+ * already on the page. Load none of them and everything still works; load
7
+ * one and Deck hands that job over to a better engine and keeps providing
8
+ * the styling.
9
+ *
10
+ * Detected, in order of how much they actually add:
11
+ *
12
+ * Floating UI window.FloatingUIDOM collision-aware placement, for
13
+ * browsers without CSS anchor
14
+ * positioning
15
+ * Quill window.Quill replaces the deprecated
16
+ * Tiptap window.Tiptap document.execCommand editor
17
+ * Chart.js window.Chart real axes, tooltips, time scales
18
+ * SortableJS window.Sortable drag reordering and kanban
19
+ * Lucide window.lucide 1500 icons instead of Deck's 56
20
+ *
21
+ * Nothing here is required and nothing here is bundled. Add a script tag for
22
+ * the ones you want.
23
+ * ======================================================================== */
24
+
25
+ (function (Deck) {
26
+ 'use strict';
27
+ if (!Deck) { console.warn('deck-adapters.js: load deck.js first'); return; }
28
+
29
+ const $ = (s, r = document) => r.querySelector(s);
30
+ const $$ = (s, r = document) => Array.from(r.querySelectorAll(s));
31
+ const once = n => { if (n.dataset.deckA) return false; n.dataset.deckA = '1'; return true; };
32
+ const css = v => getComputedStyle(document.documentElement).getPropertyValue(v).trim();
33
+ const hasAnchor = CSS.supports('anchor-name: --a');
34
+
35
+ const have = {
36
+ get floating() { return !!window.FloatingUIDOM; },
37
+ get quill() { return !!window.Quill; },
38
+ get tiptap() { return !!(window.Tiptap && window.Tiptap.Editor); },
39
+ get chart() { return !!window.Chart; },
40
+ get sortable() { return !!window.Sortable; },
41
+ get lucide() { return !!window.lucide; }
42
+ };
43
+
44
+ /* ======================================================================
45
+ 1. Floating UI — placement
46
+ Only used where CSS anchor positioning is missing. Where the browser has
47
+ anchor positioning, the CSS in 25-anchor.css already does this natively,
48
+ on the compositor, with no listeners, so the adapter stays out of the way.
49
+ ====================================================================== */
50
+
51
+ function floating(root) {
52
+ if (hasAnchor || !have.floating) return;
53
+ const { computePosition, autoUpdate, offset, flip, shift, arrow, size } = window.FloatingUIDOM;
54
+
55
+ $$('[data-deck-anchor]', root).forEach(trigger => {
56
+ if (!once(trigger)) return;
57
+ const panel = $(trigger.dataset.deckAnchor);
58
+ if (!panel) return;
59
+ const arrowEl = $('.tip-arrow', panel);
60
+ const placement = panel.dataset.placement || 'bottom';
61
+ const matchWidth = panel.classList.contains('menu-match') ||
62
+ panel.classList.contains('combo-list');
63
+
64
+ const middleware = [
65
+ offset(8),
66
+ flip({ fallbackAxisSideDirection: 'start' }),
67
+ shift({ padding: 8 }),
68
+ size({
69
+ padding: 8,
70
+ apply({ availableHeight, rects, elements }) {
71
+ Object.assign(elements.floating.style, {
72
+ maxHeight: `${Math.max(120, availableHeight)}px`,
73
+ ...(matchWidth ? { width: `${rects.reference.width}px` } : {})
74
+ });
75
+ }
76
+ })
77
+ ];
78
+ if (arrowEl) middleware.push(arrow({ element: arrowEl, padding: 6 }));
79
+
80
+ const place = async () => {
81
+ const r = await computePosition(trigger, panel, { placement, strategy: 'fixed', middleware });
82
+ Object.assign(panel.style, { left: `${r.x}px`, top: `${r.y}px`, position: 'fixed' });
83
+ if (arrowEl && r.middlewareData.arrow) {
84
+ const { x, y } = r.middlewareData.arrow;
85
+ const side = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }[r.placement.split('-')[0]];
86
+ Object.assign(arrowEl.style, {
87
+ left: x != null ? `${x}px` : '',
88
+ top: y != null ? `${y}px` : '',
89
+ [side]: '-4px'
90
+ });
91
+ }
92
+ };
93
+
94
+ let stop = null;
95
+ panel.addEventListener('toggle', e => {
96
+ if (e.newState === 'open') { place(); stop = autoUpdate(trigger, panel, place); }
97
+ else { stop?.(); stop = null; }
98
+ });
99
+ });
100
+ }
101
+
102
+ /* ======================================================================
103
+ 2. Editor — Quill or Tiptap
104
+ Deck's own editor uses document.execCommand, which is deprecated and
105
+ inconsistent. If either library is on the page, Deck hands the content
106
+ region over and keeps its own toolbar chrome, so the markup and the CSS
107
+ do not change.
108
+ ====================================================================== */
109
+
110
+ function editorAdapter(root) {
111
+ if (!have.quill && !have.tiptap) return;
112
+
113
+ $$('.editor', root).forEach(ed => {
114
+ if (!once(ed)) return;
115
+ const content = $('.editor-content', ed);
116
+ const target = ed.dataset.target ? $(ed.dataset.target) : null;
117
+ const counter = $('.editor-count', ed);
118
+ const limit = Number(ed.dataset.limit || 0);
119
+ if (!content) return;
120
+
121
+ // Deck's own handler already owns this node; take it back cleanly
122
+ content.contentEditable = 'false';
123
+ content.removeAttribute('role');
124
+
125
+ const count = text => {
126
+ if (!counter) return;
127
+ const n = text.trim().length;
128
+ counter.textContent = limit ? `${n} / ${limit}` : `${n} characters`;
129
+ counter.classList.toggle('is-over', limit > 0 && n > limit);
130
+ };
131
+
132
+ if (have.tiptap) {
133
+ const { Editor } = window.Tiptap;
134
+ const extensions = window.Tiptap.StarterKit ? [window.Tiptap.StarterKit] : [];
135
+ const editor = new Editor({
136
+ element: content,
137
+ extensions,
138
+ content: target?.value || content.innerHTML || '',
139
+ onUpdate: ({ editor }) => {
140
+ if (target) target.value = editor.getHTML();
141
+ count(editor.getText());
142
+ paint(editor);
143
+ ed.dispatchEvent(new CustomEvent('deck:change', {
144
+ bubbles: true, detail: { html: editor.getHTML(), text: editor.getText() }
145
+ }));
146
+ }
147
+ });
148
+
149
+ const CMD = {
150
+ bold: e => e.chain().focus().toggleBold().run(),
151
+ italic: e => e.chain().focus().toggleItalic().run(),
152
+ underline: e => e.chain().focus().toggleUnderline?.().run(),
153
+ strikeThrough: e => e.chain().focus().toggleStrike().run(),
154
+ insertUnorderedList: e => e.chain().focus().toggleBulletList().run(),
155
+ insertOrderedList: e => e.chain().focus().toggleOrderedList().run(),
156
+ formatBlock: (e, v) => v === 'p'
157
+ ? e.chain().focus().setParagraph().run()
158
+ : e.chain().focus().toggleHeading({ level: Number(v.replace('h', '')) }).run(),
159
+ undo: e => e.chain().focus().undo().run(),
160
+ redo: e => e.chain().focus().redo().run(),
161
+ createLink: e => {
162
+ const url = prompt('Link address');
163
+ if (url) e.chain().focus().setLink({ href: url }).run();
164
+ }
165
+ };
166
+ const STATE = { bold: 'bold', italic: 'italic', underline: 'underline',
167
+ strikeThrough: 'strike', insertUnorderedList: 'bulletList',
168
+ insertOrderedList: 'orderedList' };
169
+ const paint = e => $$('[data-cmd]', ed).forEach(b => {
170
+ const name = STATE[b.dataset.cmd];
171
+ if (name) b.setAttribute('aria-pressed', String(e.isActive(name)));
172
+ });
173
+
174
+ ed.addEventListener('click', e => {
175
+ const b = e.target.closest('[data-cmd]');
176
+ if (!b) return;
177
+ e.preventDefault();
178
+ CMD[b.dataset.cmd]?.(editor, b.dataset.value);
179
+ paint(editor);
180
+ });
181
+ $$('.editor-select', ed).forEach(sel =>
182
+ sel.addEventListener('change', () => CMD.formatBlock(editor, sel.value)));
183
+ ed.deckEditor = editor;
184
+ count(editor.getText());
185
+
186
+ } else {
187
+ const q = new window.Quill(content, {
188
+ theme: null, // Deck supplies the chrome
189
+ placeholder: content.dataset.placeholder || '',
190
+ modules: { toolbar: false }
191
+ });
192
+ if (target?.value) q.clipboard.dangerouslyPasteHTML(target.value);
193
+
194
+ const paint = () => {
195
+ const f = q.getFormat();
196
+ $$('[data-cmd]', ed).forEach(b => {
197
+ const map = { bold: 'bold', italic: 'italic', underline: 'underline',
198
+ strikeThrough: 'strike' };
199
+ const key = map[b.dataset.cmd];
200
+ if (key) b.setAttribute('aria-pressed', String(!!f[key]));
201
+ if (b.dataset.cmd === 'insertUnorderedList')
202
+ b.setAttribute('aria-pressed', String(f.list === 'bullet'));
203
+ if (b.dataset.cmd === 'insertOrderedList')
204
+ b.setAttribute('aria-pressed', String(f.list === 'ordered'));
205
+ });
206
+ };
207
+
208
+ const CMD = {
209
+ bold: () => q.format('bold', !q.getFormat().bold),
210
+ italic: () => q.format('italic', !q.getFormat().italic),
211
+ underline: () => q.format('underline', !q.getFormat().underline),
212
+ strikeThrough: () => q.format('strike', !q.getFormat().strike),
213
+ insertUnorderedList: () => q.format('list', q.getFormat().list === 'bullet' ? false : 'bullet'),
214
+ insertOrderedList: () => q.format('list', q.getFormat().list === 'ordered' ? false : 'ordered'),
215
+ formatBlock: v => q.format('header', v === 'p' ? false : Number(v.replace('h', ''))),
216
+ undo: () => q.history.undo(),
217
+ redo: () => q.history.redo(),
218
+ createLink: () => { const u = prompt('Link address'); if (u) q.format('link', u); }
219
+ };
220
+
221
+ ed.addEventListener('click', e => {
222
+ const b = e.target.closest('[data-cmd]');
223
+ if (!b) return;
224
+ e.preventDefault();
225
+ CMD[b.dataset.cmd]?.(b.dataset.value);
226
+ paint();
227
+ });
228
+ $$('.editor-select', ed).forEach(sel =>
229
+ sel.addEventListener('change', () => CMD.formatBlock(sel.value)));
230
+
231
+ q.on('text-change', () => {
232
+ if (target) target.value = q.root.innerHTML;
233
+ count(q.getText());
234
+ ed.dispatchEvent(new CustomEvent('deck:change', {
235
+ bubbles: true, detail: { html: q.root.innerHTML, text: q.getText() }
236
+ }));
237
+ });
238
+ q.on('selection-change', paint);
239
+ ed.deckEditor = q;
240
+ count(q.getText());
241
+ }
242
+ });
243
+ }
244
+
245
+ /* ======================================================================
246
+ 3. Chart.js — themed from Deck's tokens
247
+ Deck's CSS charts are good for dashboard tiles and they retheme with the
248
+ hue slider. What they cannot do is a time axis, a crosshair, a zoom, or
249
+ twenty thousand points. Where Chart.js is loaded, this makes it match
250
+ Deck exactly and follow the theme and the hue at runtime.
251
+ ====================================================================== */
252
+
253
+ function chartTheme() {
254
+ if (!have.chart) return null;
255
+ const Chart = window.Chart;
256
+
257
+ const series = i => css(`--c${(i % 6) + 1}`) || css('--brand-500');
258
+
259
+ const apply = () => {
260
+ Chart.defaults.font.family = css('--font-sans');
261
+ Chart.defaults.font.size = 12;
262
+ Chart.defaults.color = css('--text-muted');
263
+ Chart.defaults.borderColor = css('--line');
264
+ Chart.defaults.elements.line.tension = 0.3;
265
+ Chart.defaults.elements.line.borderWidth = 2;
266
+ Chart.defaults.elements.point.radius = 0;
267
+ Chart.defaults.elements.point.hoverRadius = 5;
268
+ Chart.defaults.elements.bar.borderRadius = 4;
269
+ Chart.defaults.elements.bar.borderSkipped = 'bottom';
270
+
271
+ Chart.defaults.plugins.legend.labels.usePointStyle = true;
272
+ Chart.defaults.plugins.legend.labels.boxWidth = 8;
273
+ Chart.defaults.plugins.legend.labels.boxHeight = 8;
274
+ Chart.defaults.plugins.legend.labels.padding = 14;
275
+
276
+ Object.assign(Chart.defaults.plugins.tooltip, {
277
+ backgroundColor: css('--ink-900'),
278
+ titleColor: css('--ink-50'),
279
+ bodyColor: css('--ink-100'),
280
+ borderColor: 'transparent',
281
+ cornerRadius: 6,
282
+ padding: 10,
283
+ displayColors: true,
284
+ usePointStyle: true,
285
+ boxPadding: 4,
286
+ titleFont: { weight: '620' }
287
+ });
288
+
289
+ Chart.defaults.scale.grid.color = css('--line');
290
+ Chart.defaults.scale.grid.drawTicks = false;
291
+ Chart.defaults.scale.border = { display: false };
292
+ Chart.defaults.scale.ticks.padding = 8;
293
+ };
294
+
295
+ apply();
296
+
297
+ // Follow theme and hue changes without rebuilding the chart
298
+ const repaint = () => {
299
+ apply();
300
+ Object.values(Chart.instances || {}).forEach(c => {
301
+ c.data.datasets.forEach((d, i) => {
302
+ if (d.deckSeries !== false) {
303
+ d.borderColor = series(i);
304
+ d.backgroundColor = d.fill ? `color-mix(in oklab, ${series(i)} 18%, transparent)` : series(i);
305
+ }
306
+ });
307
+ c.update('none');
308
+ });
309
+ };
310
+ new MutationObserver(repaint).observe(document.documentElement, {
311
+ attributes: true, attributeFilter: ['data-theme', 'style']
312
+ });
313
+ matchMedia('(prefers-color-scheme: dark)').addEventListener('change', repaint);
314
+
315
+ // Convenience: Deck.chart(canvas, config) applies the series palette
316
+ Deck.chart = (canvas, config) => {
317
+ (config.data?.datasets || []).forEach((d, i) => {
318
+ if (d.borderColor == null) d.borderColor = series(i);
319
+ if (d.backgroundColor == null) {
320
+ d.backgroundColor = d.fill
321
+ ? `color-mix(in oklab, ${series(i)} 18%, transparent)`
322
+ : series(i);
323
+ }
324
+ });
325
+ return new Chart(canvas, config);
326
+ };
327
+
328
+ return { apply, repaint, series };
329
+ }
330
+
331
+ /* ======================================================================
332
+ 4. SortableJS — drag reordering and kanban
333
+ Deck's CSS already uses SortableJS's default class names, so no
334
+ configuration is needed. Without the library, the fallback below uses the
335
+ native HTML drag and drop API, which is worse on touch but costs nothing.
336
+ ====================================================================== */
337
+
338
+ function sortables(root) {
339
+ $$('[data-deck-sortable]', root).forEach(list => {
340
+ if (!once(list)) return;
341
+ const group = list.dataset.deckSortable || undefined;
342
+ const handle = list.dataset.handle || null;
343
+
344
+ const emit = detail => list.dispatchEvent(
345
+ new CustomEvent('deck:reorder', { bubbles: true, detail }));
346
+
347
+ if (have.sortable) {
348
+ window.Sortable.create(list, {
349
+ group: group && group !== 'true' ? { name: group, pull: true, put: true } : undefined,
350
+ handle,
351
+ animation: Deck.reduced?.() ? 0 : 180,
352
+ easing: 'cubic-bezier(.22,1,.36,1)',
353
+ ghostClass: 'sortable-ghost',
354
+ chosenClass: 'sortable-chosen',
355
+ dragClass: 'sortable-drag',
356
+ forceFallback: false,
357
+ fallbackOnBody: true,
358
+ swapThreshold: 0.66,
359
+ delay: matchMedia('(pointer: coarse)').matches ? 140 : 0,
360
+ delayOnTouchOnly: true,
361
+ onEnd: e => {
362
+ counts();
363
+ emit({
364
+ item: e.item, from: e.from, to: e.to,
365
+ oldIndex: e.oldIndex, newIndex: e.newIndex,
366
+ order: Array.from(e.to.children).map(c => c.dataset.id ?? null)
367
+ });
368
+ }
369
+ });
370
+ return;
371
+ }
372
+
373
+ // Native fallback
374
+ Array.from(list.children).forEach(child => { child.draggable = true; });
375
+ let dragged = null;
376
+ list.addEventListener('dragstart', e => {
377
+ dragged = e.target.closest('[draggable="true"]');
378
+ if (!dragged) return;
379
+ dragged.classList.add('sortable-chosen');
380
+ e.dataTransfer.effectAllowed = 'move';
381
+ e.dataTransfer.setData('text/plain', dragged.dataset.id || '');
382
+ });
383
+ list.addEventListener('dragover', e => {
384
+ e.preventDefault();
385
+ const over = e.target.closest('[draggable="true"]');
386
+ if (!over || over === dragged || !dragged) return;
387
+ const r = over.getBoundingClientRect();
388
+ const after = (e.clientY - r.top) / r.height > 0.5;
389
+ over.parentElement.insertBefore(dragged, after ? over.nextSibling : over);
390
+ });
391
+ list.addEventListener('dragend', () => {
392
+ dragged?.classList.remove('sortable-chosen');
393
+ counts();
394
+ emit({ item: dragged, from: list, to: list,
395
+ order: Array.from(list.children).map(c => c.dataset.id ?? null) });
396
+ dragged = null;
397
+ });
398
+ });
399
+
400
+ function counts() {
401
+ $$('.kanban-col').forEach(col => {
402
+ const n = $$('.kanban-card', col).length;
403
+ const badge = $('.kanban-count', col);
404
+ if (badge) badge.textContent = n;
405
+ });
406
+ }
407
+ counts();
408
+ }
409
+
410
+ /* ======================================================================
411
+ 5. Lucide — 1500 icons instead of Deck's 56
412
+ Deck's sprite covers the framework's own needs and the automotive set it
413
+ was built for. For anything beyond that, write the Lucide name and this
414
+ swaps in the path data, keeping Deck's .icon sizing and stroke rules.
415
+ ====================================================================== */
416
+
417
+ function lucideIcons(root) {
418
+ if (!have.lucide) return;
419
+ const toPascal = s => s.replace(/(^\w|-\w)/g, m => m.replace('-', '').toUpperCase());
420
+
421
+ $$('[data-icon]', root).forEach(node => {
422
+ if (!once(node)) return;
423
+ const name = node.dataset.icon;
424
+ const node_ = window.lucide.icons?.[toPascal(name)] || window.lucide[toPascal(name)];
425
+ if (!node_) { console.warn(`deck: no Lucide icon "${name}"`); return; }
426
+ const children = Array.isArray(node_) ? node_[2] : node_.children || [];
427
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
428
+ svg.setAttribute('viewBox', '0 0 24 24');
429
+ svg.setAttribute('aria-hidden', 'true');
430
+ svg.setAttribute('class', node.className || 'icon');
431
+ children.forEach(([tag, attrs]) => {
432
+ const el = document.createElementNS('http://www.w3.org/2000/svg', tag);
433
+ Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v));
434
+ svg.append(el);
435
+ });
436
+ node.replaceWith(svg);
437
+ });
438
+ }
439
+
440
+ /* ======================================================================
441
+ Register
442
+ ====================================================================== */
443
+
444
+ const baseInit = Deck.init.bind(Deck);
445
+ Deck.init = function (root = document) {
446
+ baseInit(root);
447
+ floating(root);
448
+ editorAdapter(root);
449
+ sortables(root);
450
+ lucideIcons(root);
451
+ return Deck;
452
+ };
453
+
454
+ Deck.adapters = {
455
+ get available() {
456
+ return Object.fromEntries(Object.keys(have).map(k => [k, have[k]]));
457
+ },
458
+ /* What is actually doing the work right now. Useful in a console when a
459
+ component behaves differently between two pages. */
460
+ report() {
461
+ const a = this.available;
462
+ return {
463
+ placement: hasAnchor ? 'CSS anchor positioning'
464
+ : a.floating ? 'Floating UI'
465
+ : 'Deck built-in',
466
+ editor: a.tiptap ? 'Tiptap' : a.quill ? 'Quill' : 'Deck built-in (execCommand)',
467
+ charts: a.chart ? 'Chart.js, themed by Deck' : 'Deck CSS charts',
468
+ dragDrop: a.sortable ? 'SortableJS' : 'native drag and drop',
469
+ icons: a.lucide ? 'Lucide + Deck sprite' : 'Deck sprite (74 icons)',
470
+ virtualization: CSS.supports('content-visibility: auto')
471
+ ? 'content-visibility (engine)' : 'none'
472
+ };
473
+ }
474
+ };
475
+
476
+ if (have.chart) Deck.chartTheme = chartTheme();
477
+
478
+ if (document.readyState !== 'loading') Deck.init();
479
+ else document.addEventListener('DOMContentLoaded', () => Deck.init());
480
+
481
+ })(typeof window !== 'undefined' && window.Deck ? window.Deck
482
+ : typeof Deck !== 'undefined' ? Deck : null);