elvish-css 1.0.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +518 -0
  3. package/dist/elvish.css +2194 -0
  4. package/dist/elvish.d.ts +78 -0
  5. package/dist/elvish.esm.js +2185 -0
  6. package/dist/elvish.iife.js +2169 -0
  7. package/dist/elvish.min.css +9 -0
  8. package/dist/elvish.umd.js +2173 -0
  9. package/elvish.css +28 -0
  10. package/elvish.js +81 -0
  11. package/global/global.css +16 -0
  12. package/global/modern.css +305 -0
  13. package/global/reset.css +507 -0
  14. package/global/tokens.css +154 -0
  15. package/global/transitions.css +288 -0
  16. package/global/transitions.js +289 -0
  17. package/global/utilities.css +151 -0
  18. package/package.json +61 -0
  19. package/primitives/adleithian/adleithian.css +16 -0
  20. package/primitives/adleithian/adleithian.js +63 -0
  21. package/primitives/bau/bau.css +86 -0
  22. package/primitives/bau/bau.js +127 -0
  23. package/primitives/enedh/enedh.css +38 -0
  24. package/primitives/enedh/enedh.js +110 -0
  25. package/primitives/esgal/esgal.css +39 -0
  26. package/primitives/esgal/esgal.js +115 -0
  27. package/primitives/fano/fano.css +28 -0
  28. package/primitives/fano/fano.js +108 -0
  29. package/primitives/gant-thala/gant-thala.css +32 -0
  30. package/primitives/gant-thala/gant-thala.js +69 -0
  31. package/primitives/glan-tholl/glan-tholl.css +71 -0
  32. package/primitives/glan-tholl/glan-tholl.js +147 -0
  33. package/primitives/glan-veleg/glan-veleg.css +45 -0
  34. package/primitives/glan-veleg/glan-veleg.js +138 -0
  35. package/primitives/gonath/gonath.css +57 -0
  36. package/primitives/gonath/gonath.js +113 -0
  37. package/primitives/gwistindor/gwistindor.css +52 -0
  38. package/primitives/gwistindor/gwistindor.js +96 -0
  39. package/primitives/hath/hath.css +39 -0
  40. package/primitives/hath/hath.js +107 -0
  41. package/primitives/him/him.css +43 -0
  42. package/primitives/him/him.js +169 -0
  43. package/primitives/miriant/miriant.css +75 -0
  44. package/primitives/miriant/miriant.js +158 -0
  45. package/primitives/thann/thann.css +57 -0
  46. package/primitives/thann/thann.js +96 -0
  47. package/primitives/tiniath/tiniath.css +16 -0
  48. package/primitives/tiniath/tiniath.js +88 -0
  49. package/primitives/vircantie/vircantie.css +24 -0
  50. package/primitives/vircantie/vircantie.js +83 -0
@@ -0,0 +1,2173 @@
1
+ /**
2
+ * Elvish CSS Layout System v1.0.0
3
+ * Custom Elements for intrinsic CSS layouts
4
+ *
5
+ * https://github.com/star-this/elvish-css
6
+ * License: MIT
7
+ */
8
+
9
+
10
+ (function(global, factory) {
11
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
12
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
13
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Elvish = {}));
14
+ })(this, function(exports) {
15
+ 'use strict';
16
+
17
+ // Transitions
18
+ /**
19
+ * Elvish - View Transitions Helper
20
+ *
21
+ * Utilities for smooth view transitions between layout states.
22
+ *
23
+ * Usage:
24
+ *
25
+ * import { transition, transitionTo } from './transitions.js';
26
+ *
27
+ * // Wrap any DOM change in a transition
28
+ * transition(() => {
29
+ * element.classList.toggle('collapsed');
30
+ * });
31
+ *
32
+ * // With custom options
33
+ * transition(() => {
34
+ * sidebar.hidden = true;
35
+ * }, { duration: 500 });
36
+ *
37
+ * // Transition with callback after completion
38
+ * transition(() => {
39
+ * grid.setAttribute('columns', '4');
40
+ * }).then(() => {
41
+ * console.log('Transition complete!');
42
+ * });
43
+ */
44
+
45
+ /**
46
+ * Check if View Transitions API is supported
47
+ */
48
+ var supportsViewTransitions = () =>
49
+ typeof document !== 'undefined' &&
50
+ 'startViewTransition' in document;
51
+
52
+ /**
53
+ * Get the currently active view transition (if any)
54
+ * @returns {ViewTransition|null}
55
+ */
56
+ var getActiveTransition = () =>
57
+ document.activeViewTransition ?? null;
58
+
59
+ /**
60
+ * Wrap a DOM mutation in a view transition
61
+ *
62
+ * @param {Function} updateCallback - Function that performs DOM changes
63
+ * @param {Object} options - Configuration options
64
+ * @param {number} options.duration - Override transition duration (ms)
65
+ * @param {string} options.easing - Override easing function
66
+ * @param {string[]} options.types - Transition types for :active-view-transition-type()
67
+ * @returns {Promise} Resolves when transition completes
68
+ */
69
+ function transition(updateCallback, options = {}) {
70
+ // If View Transitions not supported, just run the callback
71
+ if (!supportsViewTransitions()) {
72
+ updateCallback();
73
+ return Promise.resolve();
74
+ }
75
+
76
+ // Apply custom duration/easing if provided
77
+ var root = document.documentElement;
78
+ var originalDuration = root.style.getPropertyValue('--transition-duration');
79
+ var originalEasing = root.style.getPropertyValue('--transition-ease');
80
+
81
+ if (options.duration) {
82
+ root.style.setProperty('--transition-duration', `${options.duration}ms`);
83
+ }
84
+ if (options.easing) {
85
+ root.style.setProperty('--transition-ease', options.easing);
86
+ }
87
+
88
+ // Start the view transition with optional types
89
+ var transitionOptions = options.types
90
+ ? { update: updateCallback, types: options.types }
91
+ : updateCallback;
92
+
93
+ var viewTransition = document.startViewTransition(transitionOptions);
94
+
95
+ // Restore original values after transition
96
+ return viewTransition.finished.then(() => {
97
+ if (options.duration) {
98
+ root.style.setProperty('--transition-duration', originalDuration || '');
99
+ }
100
+ if (options.easing) {
101
+ root.style.setProperty('--transition-ease', originalEasing || '');
102
+ }
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Transition an element to a new state by changing attributes/classes
108
+ *
109
+ * @param {HTMLElement} element - Element to transition
110
+ * @param {Object} changes - Attribute/class changes to apply
111
+ * @param {Object} changes.attrs - Attributes to set (use null to remove)
112
+ * @param {string[]} changes.addClass - Classes to add
113
+ * @param {string[]} changes.removeClass - Classes to remove
114
+ * @param {string[]} changes.toggleClass - Classes to toggle
115
+ * @param {Object} changes.style - Inline styles to set
116
+ * @param {Object} options - Transition options
117
+ * @returns {Promise}
118
+ */
119
+ function transitionTo(element, changes = {}, options = {}) {
120
+ return transition(() => {
121
+ // Apply attribute changes
122
+ if (changes.attrs) {
123
+ Object.entries(changes.attrs).forEach(([key, value]) => {
124
+ if (value === null) {
125
+ element.removeAttribute(key);
126
+ } else {
127
+ element.setAttribute(key, value);
128
+ }
129
+ });
130
+ }
131
+
132
+ // Apply class changes
133
+ if (changes.addClass) {
134
+ element.classList.add(...changes.addClass);
135
+ }
136
+ if (changes.removeClass) {
137
+ element.classList.remove(...changes.removeClass);
138
+ }
139
+ if (changes.toggleClass) {
140
+ changes.toggleClass.forEach(cls => element.classList.toggle(cls));
141
+ }
142
+
143
+ // Apply style changes
144
+ if (changes.style) {
145
+ Object.entries(changes.style).forEach(([prop, value]) => {
146
+ element.style.setProperty(prop, value);
147
+ });
148
+ }
149
+ }, options);
150
+ }
151
+
152
+ /**
153
+ * Transition between two elements (e.g., swap content)
154
+ *
155
+ * @param {HTMLElement} outElement - Element leaving
156
+ * @param {HTMLElement} inElement - Element entering
157
+ * @param {Object} options - Transition options
158
+ * @returns {Promise}
159
+ */
160
+ function crossfade(outElement, inElement, options = {}) {
161
+ return transition(() => {
162
+ outElement.hidden = true;
163
+ inElement.hidden = false;
164
+ }, options);
165
+ }
166
+
167
+ /**
168
+ * Create a named transition group for an element
169
+ * Elements with the same view-transition-name animate together
170
+ *
171
+ * @param {HTMLElement} element - Element to name
172
+ * @param {string} name - Transition name
173
+ */
174
+ function setTransitionName(element, name) {
175
+ element.style.viewTransitionName = name;
176
+ }
177
+
178
+ /**
179
+ * Remove transition name from element
180
+ *
181
+ * @param {HTMLElement} element
182
+ */
183
+ function clearTransitionName(element) {
184
+ element.style.viewTransitionName = '';
185
+ }
186
+
187
+ /**
188
+ * Batch multiple elements with unique transition names
189
+ * Useful for list/grid items that should animate independently
190
+ *
191
+ * @param {NodeList|HTMLElement[]} elements - Elements to name
192
+ * @param {string} prefix - Name prefix (will add index)
193
+ */
194
+ function nameTransitionGroup(elements, prefix = 'item') {
195
+ elements.forEach((el, i) => {
196
+ el.style.viewTransitionName = `${prefix}-${i}`;
197
+ });
198
+ }
199
+
200
+ /**
201
+ * Enable auto-naming with match-element for a container's children
202
+ * Each child gets a unique browser-generated name automatically
203
+ *
204
+ * @param {HTMLElement} container - Parent element
205
+ * @param {string} [transitionClass] - Optional view-transition-class for group styling
206
+ */
207
+ function enableAutoNaming(container, transitionClass) {
208
+ Array.from(container.children).forEach(child => {
209
+ child.style.viewTransitionName = 'match-element';
210
+ if (transitionClass) {
211
+ child.style.viewTransitionClass = transitionClass;
212
+ }
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Disable auto-naming for a container's children
218
+ *
219
+ * @param {HTMLElement} container - Parent element
220
+ */
221
+ function disableAutoNaming(container) {
222
+ Array.from(container.children).forEach(child => {
223
+ child.style.viewTransitionName = '';
224
+ child.style.viewTransitionClass = '';
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Elvish-specific: Transition ratio change
230
+ *
231
+ * @param {'golden'|'silver'|'fifth'} ratio - New ratio
232
+ * @param {Object} options - Transition options
233
+ */
234
+ function transitionRatio(ratio, options = {}) {
235
+ var ratioVar = `var(--ratio-${ratio})`;
236
+ var measures = { golden: '60ch', fifth: '70ch', silver: '80ch' };
237
+
238
+ return transition(() => {
239
+ var root = document.documentElement;
240
+ root.style.setProperty('--ratio', ratioVar);
241
+ root.style.setProperty('--measure', measures[ratio]);
242
+ root.dataset.ratio = ratio;
243
+ }, { duration: 400, types: ['layout', 'ratio'], ...options });
244
+ }
245
+
246
+ /**
247
+ * Elvish-specific: Transition theme change
248
+ *
249
+ * @param {'light'|'dark'|'auto'} theme - New theme
250
+ * @param {Object} options - Transition options
251
+ */
252
+ function transitionTheme(theme, options = {}) {
253
+ return transition(() => {
254
+ var root = document.documentElement;
255
+ if (theme === 'auto') {
256
+ root.style.colorScheme = 'light dark';
257
+ } else {
258
+ root.style.colorScheme = theme;
259
+ }
260
+ localStorage.setItem('elvish-theme', theme);
261
+ }, { duration: 300, types: ['theme'], ...options });
262
+ }
263
+
264
+ /**
265
+ * Elvish-specific: Transition layout primitive state
266
+ *
267
+ * @param {HTMLElement} primitive - Elvish layout element
268
+ * @param {Object} attrs - New attribute values
269
+ * @param {Object} options - Transition options
270
+ */
271
+ function transitionLayout(primitive, attrs, options = {}) {
272
+ // Give the primitive a transition name if it doesn't have one
273
+ if (!primitive.style.viewTransitionName) {
274
+ primitive.style.viewTransitionName = primitive.tagName.toLowerCase();
275
+ }
276
+
277
+ return transitionTo(primitive, { attrs }, { types: ['layout'], ...options });
278
+ }
279
+
280
+ /**
281
+ * Skip/cancel the currently active view transition
282
+ */
283
+ function skipTransition() {
284
+ var active = getActiveTransition();
285
+ if (active) {
286
+ active.skipTransition();
287
+ }
288
+ }
289
+
290
+ // Default export for convenient importing
291
+ default {
292
+ transition,
293
+ transitionTo,
294
+ crossfade,
295
+ setTransitionName,
296
+ clearTransitionName,
297
+ nameTransitionGroup,
298
+ enableAutoNaming,
299
+ disableAutoNaming,
300
+ transitionRatio,
301
+ transitionTheme,
302
+ transitionLayout,
303
+ skipTransition,
304
+ supportsViewTransitions,
305
+ getActiveTransition,
306
+ };
307
+
308
+
309
+ // Primitives
310
+ // hath
311
+ /**
312
+ * Stack Layout Custom Element
313
+ *
314
+ * Injects vertical margin between successive child elements.
315
+ *
316
+ * @property {string} space - A CSS margin value (default: var(--s1))
317
+ * @property {boolean} recursive - Apply to all descendants, not just children
318
+ * @property {number} splitAfter - Element index after which to split with auto margin
319
+ *
320
+ * @example
321
+ * <i-hath space="var(--s2)">
322
+ * <h2>Heading</h2>
323
+ * <p>Paragraph one</p>
324
+ * <p>Paragraph two</p>
325
+ * </i-hath>
326
+ */
327
+
328
+ class HathLayout extends HTMLElement {
329
+ static get observedAttributes() {
330
+ return ['space', 'recursive', 'split-after'];
331
+ }
332
+
333
+ constructor() {
334
+ super();
335
+ this.render = this.render.bind(this);
336
+ }
337
+
338
+ connectedCallback() {
339
+ this.render();
340
+ }
341
+
342
+ attributeChangedCallback() {
343
+ this.render();
344
+ }
345
+
346
+ get space() {
347
+ return this.getAttribute('space') || 'var(--s1)';
348
+ }
349
+
350
+ set space(val) {
351
+ this.setAttribute('space', val);
352
+ }
353
+
354
+ get recursive() {
355
+ return this.hasAttribute('recursive');
356
+ }
357
+
358
+ set recursive(val) {
359
+ if (val) {
360
+ this.setAttribute('recursive', '');
361
+ } else {
362
+ this.removeAttribute('recursive');
363
+ }
364
+ }
365
+
366
+ get splitAfter() {
367
+ const val = this.getAttribute('split-after');
368
+ return val ? parseInt(val, 10) : null;
369
+ }
370
+
371
+ set splitAfter(val) {
372
+ if (val) {
373
+ this.setAttribute('split-after', val);
374
+ } else {
375
+ this.removeAttribute('split-after');
376
+ }
377
+ }
378
+
379
+ render() {
380
+ // Generate unique identifier for this configuration
381
+ const recursive = this.recursive ? '-recursive' : '';
382
+ const split = this.splitAfter ? `-split${this.splitAfter}` : '';
383
+ const id = `Hath-${this.space}${recursive}${split}`.replace(/[^\w-]/g, '');
384
+
385
+ this.dataset.i = id;
386
+ this.style.setProperty('--hath-space', this.space);
387
+
388
+ // Check if styles already exist for this configuration
389
+ if (!document.getElementById(id)) {
390
+ const styleEl = document.createElement('style');
391
+ styleEl.id = id;
392
+
393
+ const selector = `[data-i="${id}"]`;
394
+ let css = '';
395
+
396
+ if (this.recursive) {
397
+ css = `${selector} * + * { margin-block-start: ${this.space}; }`;
398
+ } else {
399
+ css = `${selector} > * + * { margin-block-start: ${this.space}; }`;
400
+ }
401
+
402
+ if (this.splitAfter) {
403
+ css += `${selector} > :nth-child(${this.splitAfter}) { margin-block-end: auto; }`;
404
+ }
405
+
406
+ styleEl.textContent = css;
407
+ document.head.appendChild(styleEl);
408
+ }
409
+ }
410
+ }
411
+
412
+ // Define the custom element
413
+ if ('customElements' in window) {
414
+ customElements.define('i-hath', HathLayout);
415
+ }
416
+
417
+
418
+
419
+
420
+ // bau
421
+ /**
422
+ * Box Layout Custom Element
423
+ *
424
+ * A simple container with padding and optional border/background.
425
+ *
426
+ * @property {string} padding - A CSS padding value (default: var(--s1))
427
+ * @property {string} borderWidth - A CSS border-width value (default: var(--border-thin))
428
+ * @property {boolean} invert - Swap foreground and background colors
429
+ * @property {boolean} borderless - Remove border
430
+ * @property {boolean} compact - Remove padding
431
+ *
432
+ * @example
433
+ * <i-bau padding="var(--s2)" invert>
434
+ * <p>Content in a box</p>
435
+ * </i-bau>
436
+ */
437
+
438
+ class BauLayout extends HTMLElement {
439
+ static get observedAttributes() {
440
+ return ['padding', 'border-width', 'invert', 'borderless', 'compact'];
441
+ }
442
+
443
+ constructor() {
444
+ super();
445
+ this.render = this.render.bind(this);
446
+ }
447
+
448
+ connectedCallback() {
449
+ this.render();
450
+ }
451
+
452
+ attributeChangedCallback() {
453
+ this.render();
454
+ }
455
+
456
+ get padding() {
457
+ return this.getAttribute('padding') || 'var(--s1)';
458
+ }
459
+
460
+ set padding(val) {
461
+ this.setAttribute('padding', val);
462
+ }
463
+
464
+ get borderWidth() {
465
+ return this.getAttribute('border-width') || 'var(--border-thin)';
466
+ }
467
+
468
+ set borderWidth(val) {
469
+ this.setAttribute('border-width', val);
470
+ }
471
+
472
+ get invert() {
473
+ return this.hasAttribute('invert');
474
+ }
475
+
476
+ set invert(val) {
477
+ if (val) {
478
+ this.setAttribute('invert', '');
479
+ } else {
480
+ this.removeAttribute('invert');
481
+ }
482
+ }
483
+
484
+ get borderless() {
485
+ return this.hasAttribute('borderless');
486
+ }
487
+
488
+ set borderless(val) {
489
+ if (val) {
490
+ this.setAttribute('borderless', '');
491
+ } else {
492
+ this.removeAttribute('borderless');
493
+ }
494
+ }
495
+
496
+ get compact() {
497
+ return this.hasAttribute('compact');
498
+ }
499
+
500
+ set compact(val) {
501
+ if (val) {
502
+ this.setAttribute('compact', '');
503
+ } else {
504
+ this.removeAttribute('compact');
505
+ }
506
+ }
507
+
508
+ render() {
509
+ // Set CSS custom properties for this instance
510
+ this.style.setProperty('--bau-padding', this.padding);
511
+ this.style.setProperty('--bau-border-width', this.borderWidth);
512
+
513
+ // Generate unique identifier
514
+ const invertStr = this.invert ? '-invert' : '';
515
+ const borderlessStr = this.borderless ? '-borderless' : '';
516
+ const compactStr = this.compact ? '-compact' : '';
517
+ const id = `Bau-${this.padding}-${this.borderWidth}${invertStr}${borderlessStr}${compactStr}`.replace(/[^\w-]/g, '');
518
+
519
+ this.dataset.i = id;
520
+
521
+ // Check if styles already exist
522
+ if (!document.getElementById(id)) {
523
+ const styleEl = document.createElement('style');
524
+ styleEl.id = id;
525
+
526
+ const selector = `[data-i="${id}"]`;
527
+ let css = `${selector} { padding: ${this.padding}; border-width: ${this.borderWidth}; }`;
528
+
529
+ if (this.borderless) {
530
+ css += `${selector} { border-width: 0; }`;
531
+ }
532
+
533
+ if (this.compact) {
534
+ css += `${selector} { padding: 0; }`;
535
+ }
536
+
537
+ styleEl.textContent = css;
538
+ document.head.appendChild(styleEl);
539
+ }
540
+ }
541
+ }
542
+
543
+ if ('customElements' in window) {
544
+ customElements.define('i-bau', BauLayout);
545
+ }
546
+
547
+
548
+
549
+
550
+ // enedh
551
+ /**
552
+ * Center Layout Custom Element
553
+ *
554
+ * Horizontally centers content with a maximum width.
555
+ *
556
+ * @property {string} max - Maximum width (default: var(--measure))
557
+ * @property {string} gutters - Minimum space on sides (default: 0)
558
+ * @property {boolean} intrinsic - Center children based on their content width
559
+ * @property {boolean} andText - Also center text alignment
560
+ *
561
+ * @example
562
+ * <i-enedh max="40ch" gutters="var(--s1)">
563
+ * <p>Centered content</p>
564
+ * </i-enedh>
565
+ */
566
+
567
+ class EnedhLayout extends HTMLElement {
568
+ static get observedAttributes() {
569
+ return ['max', 'gutters', 'intrinsic', 'and-text'];
570
+ }
571
+
572
+ constructor() {
573
+ super();
574
+ this.render = this.render.bind(this);
575
+ }
576
+
577
+ connectedCallback() {
578
+ this.render();
579
+ }
580
+
581
+ attributeChangedCallback() {
582
+ this.render();
583
+ }
584
+
585
+ get max() {
586
+ return this.getAttribute('max') || 'var(--measure)';
587
+ }
588
+
589
+ set max(val) {
590
+ this.setAttribute('max', val);
591
+ }
592
+
593
+ get gutters() {
594
+ return this.getAttribute('gutters') || '0';
595
+ }
596
+
597
+ set gutters(val) {
598
+ this.setAttribute('gutters', val);
599
+ }
600
+
601
+ get intrinsic() {
602
+ return this.hasAttribute('intrinsic');
603
+ }
604
+
605
+ set intrinsic(val) {
606
+ if (val) {
607
+ this.setAttribute('intrinsic', '');
608
+ } else {
609
+ this.removeAttribute('intrinsic');
610
+ }
611
+ }
612
+
613
+ get andText() {
614
+ return this.hasAttribute('and-text');
615
+ }
616
+
617
+ set andText(val) {
618
+ if (val) {
619
+ this.setAttribute('and-text', '');
620
+ } else {
621
+ this.removeAttribute('and-text');
622
+ }
623
+ }
624
+
625
+ render() {
626
+ this.style.setProperty('--enedh-max', this.max);
627
+ this.style.setProperty('--enedh-gutters', this.gutters);
628
+
629
+ const intrinsicStr = this.intrinsic ? '-intrinsic' : '';
630
+ const textStr = this.andText ? '-andText' : '';
631
+ const id = `Enedh-${this.max}-${this.gutters}${intrinsicStr}${textStr}`.replace(/[^\w-]/g, '');
632
+
633
+ this.dataset.i = id;
634
+
635
+ if (!document.getElementById(id)) {
636
+ const styleEl = document.createElement('style');
637
+ styleEl.id = id;
638
+
639
+ const selector = `[data-i="${id}"]`;
640
+ let css = `${selector} { max-inline-size: ${this.max}; padding-inline: ${this.gutters}; }`;
641
+
642
+ if (this.intrinsic) {
643
+ css += `${selector} { display: flex; flex-direction: column; align-items: center; }`;
644
+ }
645
+
646
+ if (this.andText) {
647
+ css += `${selector} { text-align: center; }`;
648
+ }
649
+
650
+ styleEl.textContent = css;
651
+ document.head.appendChild(styleEl);
652
+ }
653
+ }
654
+ }
655
+
656
+ if ('customElements' in window) {
657
+ customElements.define('i-enedh', EnedhLayout);
658
+ }
659
+
660
+
661
+
662
+
663
+ // tiniath
664
+ /**
665
+ * Cluster Layout Custom Element
666
+ *
667
+ * Groups elements that differ in length and wrap naturally.
668
+ *
669
+ * @property {string} space - Gap between items (default: var(--s1))
670
+ * @property {string} justify - justify-content value (default: flex-start)
671
+ * @property {string} align - align-items value (default: center)
672
+ *
673
+ * @example
674
+ * <i-tiniath space="var(--s0)" justify="space-between">
675
+ * <button>Save</button>
676
+ * <button>Cancel</button>
677
+ * </i-tiniath>
678
+ */
679
+
680
+ class TiniathLayout extends HTMLElement {
681
+ static get observedAttributes() {
682
+ return ['space', 'justify', 'align'];
683
+ }
684
+
685
+ constructor() {
686
+ super();
687
+ this.render = this.render.bind(this);
688
+ }
689
+
690
+ connectedCallback() {
691
+ this.render();
692
+ }
693
+
694
+ attributeChangedCallback() {
695
+ this.render();
696
+ }
697
+
698
+ get space() {
699
+ return this.getAttribute('space') || 'var(--s1)';
700
+ }
701
+
702
+ set space(val) {
703
+ this.setAttribute('space', val);
704
+ }
705
+
706
+ get justify() {
707
+ return this.getAttribute('justify') || 'flex-start';
708
+ }
709
+
710
+ set justify(val) {
711
+ this.setAttribute('justify', val);
712
+ }
713
+
714
+ get align() {
715
+ return this.getAttribute('align') || 'center';
716
+ }
717
+
718
+ set align(val) {
719
+ this.setAttribute('align', val);
720
+ }
721
+
722
+ render() {
723
+ this.style.setProperty('--tiniath-space', this.space);
724
+ this.style.setProperty('--tiniath-justify', this.justify);
725
+ this.style.setProperty('--tiniath-align', this.align);
726
+
727
+ const id = `Tiniath-${this.space}-${this.justify}-${this.align}`.replace(/[^\w-]/g, '');
728
+ this.dataset.i = id;
729
+
730
+ if (!document.getElementById(id)) {
731
+ const styleEl = document.createElement('style');
732
+ styleEl.id = id;
733
+
734
+ const selector = `[data-i="${id}"]`;
735
+ styleEl.textContent = `
736
+ ${selector} {
737
+ gap: ${this.space};
738
+ justify-content: ${this.justify};
739
+ align-items: ${this.align};
740
+ }
741
+ `;
742
+ document.head.appendChild(styleEl);
743
+ }
744
+ }
745
+ }
746
+
747
+ if ('customElements' in window) {
748
+ customElements.define('i-tiniath', TiniathLayout);
749
+ }
750
+
751
+
752
+
753
+
754
+ // glan-veleg
755
+ /**
756
+ * GlanVeleg Layout Custom Element
757
+ *
758
+ * A quantum layout with one fixed-width element and one fluid element.
759
+ *
760
+ * @property {string} side - Which side is the sidebar: "left" or "right" (default: left)
761
+ * @property {string} sideWidth - Width of sidebar when horizontal (default: 20rem)
762
+ * @property {string} contentMin - Min width of content before wrapping (default: 50%)
763
+ * @property {string} space - Gap between elements (default: var(--s1))
764
+ * @property {boolean} noStretch - Disable equal height (default: false)
765
+ *
766
+ * @example
767
+ * <i-glan-veleg side-width="15rem" content-min="60%">
768
+ * <nav>GlanVeleg content</nav>
769
+ * <main>Main content</main>
770
+ * </i-glan-veleg>
771
+ */
772
+
773
+ class GlanVelegLayout extends HTMLElement {
774
+ static get observedAttributes() {
775
+ return ['side', 'side-width', 'content-min', 'space', 'no-stretch'];
776
+ }
777
+
778
+ constructor() {
779
+ super();
780
+ this.render = this.render.bind(this);
781
+ }
782
+
783
+ connectedCallback() {
784
+ this.render();
785
+ }
786
+
787
+ attributeChangedCallback() {
788
+ this.render();
789
+ }
790
+
791
+ get side() {
792
+ return this.getAttribute('side') || 'left';
793
+ }
794
+
795
+ set side(val) {
796
+ this.setAttribute('side', val);
797
+ }
798
+
799
+ get sideWidth() {
800
+ return this.getAttribute('side-width') || '20rem';
801
+ }
802
+
803
+ set sideWidth(val) {
804
+ this.setAttribute('side-width', val);
805
+ }
806
+
807
+ get contentMin() {
808
+ return this.getAttribute('content-min') || '50%';
809
+ }
810
+
811
+ set contentMin(val) {
812
+ this.setAttribute('content-min', val);
813
+ }
814
+
815
+ get space() {
816
+ return this.getAttribute('space') || 'var(--s1)';
817
+ }
818
+
819
+ set space(val) {
820
+ this.setAttribute('space', val);
821
+ }
822
+
823
+ get noStretch() {
824
+ return this.hasAttribute('no-stretch');
825
+ }
826
+
827
+ set noStretch(val) {
828
+ if (val) {
829
+ this.setAttribute('no-stretch', '');
830
+ } else {
831
+ this.removeAttribute('no-stretch');
832
+ }
833
+ }
834
+
835
+ render() {
836
+ this.style.setProperty('--glan-veleg-width', this.sideWidth);
837
+ this.style.setProperty('--glan-veleg-content-min', this.contentMin);
838
+ this.style.setProperty('--glan-veleg-space', this.space);
839
+
840
+ const sideStr = this.side === 'right' ? '-right' : '-left';
841
+ const stretchStr = this.noStretch ? '-noStretch' : '';
842
+ const id = `GlanVeleg-${this.sideWidth}-${this.contentMin}-${this.space}${sideStr}${stretchStr}`.replace(/[^\w-]/g, '');
843
+
844
+ this.dataset.i = id;
845
+
846
+ if (!document.getElementById(id)) {
847
+ const styleEl = document.createElement('style');
848
+ styleEl.id = id;
849
+
850
+ const selector = `[data-i="${id}"]`;
851
+ let css = `${selector} { gap: ${this.space}; }`;
852
+
853
+ if (this.side === 'right') {
854
+ css += `
855
+ ${selector} > :first-child {
856
+ flex-basis: 0;
857
+ flex-grow: 999;
858
+ min-inline-size: ${this.contentMin};
859
+ }
860
+ ${selector} > :last-child {
861
+ flex-basis: ${this.sideWidth};
862
+ flex-grow: 1;
863
+ }
864
+ `;
865
+ } else {
866
+ css += `
867
+ ${selector} > :first-child {
868
+ flex-basis: ${this.sideWidth};
869
+ }
870
+ ${selector} > :last-child {
871
+ flex-basis: 0;
872
+ flex-grow: 999;
873
+ min-inline-size: ${this.contentMin};
874
+ }
875
+ `;
876
+ }
877
+
878
+ if (this.noStretch) {
879
+ css += `${selector} { align-items: flex-start; }`;
880
+ }
881
+
882
+ styleEl.textContent = css;
883
+ document.head.appendChild(styleEl);
884
+ }
885
+ }
886
+ }
887
+
888
+ if ('customElements' in window) {
889
+ customElements.define('i-glan-veleg', GlanVelegLayout);
890
+ }
891
+
892
+
893
+
894
+
895
+ // gwistindor
896
+ /**
897
+ * Switcher Layout Custom Element
898
+ *
899
+ * Switches between horizontal and vertical at a container threshold.
900
+ * Uses the "Holy Albatross" technique - no intermediary states.
901
+ *
902
+ * @property {string} threshold - Container width to switch at (default: var(--measure))
903
+ * @property {string} space - Gap between elements (default: var(--s1))
904
+ * @property {number} limit - Max items for horizontal layout (default: 4)
905
+ *
906
+ * @example
907
+ * <i-gwistindor threshold="30rem" limit="3">
908
+ * <div>One</div>
909
+ * <div>Two</div>
910
+ * <div>Three</div>
911
+ * </i-gwistindor>
912
+ */
913
+
914
+ class GwistindorLayout extends HTMLElement {
915
+ static get observedAttributes() {
916
+ return ['threshold', 'space', 'limit'];
917
+ }
918
+
919
+ constructor() {
920
+ super();
921
+ this.render = this.render.bind(this);
922
+ }
923
+
924
+ connectedCallback() {
925
+ this.render();
926
+ }
927
+
928
+ attributeChangedCallback() {
929
+ this.render();
930
+ }
931
+
932
+ get threshold() {
933
+ return this.getAttribute('threshold') || 'var(--measure)';
934
+ }
935
+
936
+ set threshold(val) {
937
+ this.setAttribute('threshold', val);
938
+ }
939
+
940
+ get space() {
941
+ return this.getAttribute('space') || 'var(--s1)';
942
+ }
943
+
944
+ set space(val) {
945
+ this.setAttribute('space', val);
946
+ }
947
+
948
+ get limit() {
949
+ return parseInt(this.getAttribute('limit'), 10) || 4;
950
+ }
951
+
952
+ set limit(val) {
953
+ this.setAttribute('limit', val);
954
+ }
955
+
956
+ render() {
957
+ this.style.setProperty('--gwistindor-threshold', this.threshold);
958
+ this.style.setProperty('--gwistindor-space', this.space);
959
+
960
+ const id = `Gwistindor-${this.threshold}-${this.space}-${this.limit}`.replace(/[^\w-]/g, '');
961
+ this.dataset.i = id;
962
+
963
+ if (!document.getElementById(id)) {
964
+ const styleEl = document.createElement('style');
965
+ styleEl.id = id;
966
+
967
+ const selector = `[data-i="${id}"]`;
968
+ const limitPlusOne = this.limit + 1;
969
+
970
+ styleEl.textContent = `
971
+ ${selector} {
972
+ gap: ${this.space};
973
+ }
974
+ ${selector} > * {
975
+ flex-basis: calc((${this.threshold} - 100%) * 999);
976
+ }
977
+ ${selector} > :nth-last-child(n+${limitPlusOne}),
978
+ ${selector} > :nth-last-child(n+${limitPlusOne}) ~ * {
979
+ flex-basis: 100%;
980
+ }
981
+ `;
982
+ document.head.appendChild(styleEl);
983
+ }
984
+ }
985
+ }
986
+
987
+ if ('customElements' in window) {
988
+ customElements.define('i-gwistindor', GwistindorLayout);
989
+ }
990
+
991
+
992
+
993
+
994
+ // esgal
995
+ /**
996
+ * Cover Layout Custom Element
997
+ *
998
+ * Vertically centers a principal element with optional header/footer.
999
+ *
1000
+ * @property {string} centered - Selector for the centered element (default: h1)
1001
+ * @property {string} space - Minimum space around elements (default: var(--s1))
1002
+ * @property {string} minHeight - Minimum height of cover (default: 100vh)
1003
+ * @property {boolean} noPad - Remove padding from container
1004
+ *
1005
+ * @example
1006
+ * <i-esgal centered="h2" min-height="80vh">
1007
+ * <header>Logo</header>
1008
+ * <h2>Main Title</h2>
1009
+ * <footer>Scroll down</footer>
1010
+ * </i-esgal>
1011
+ */
1012
+
1013
+ class EsgalLayout extends HTMLElement {
1014
+ static get observedAttributes() {
1015
+ return ['centered', 'space', 'min-height', 'no-pad'];
1016
+ }
1017
+
1018
+ constructor() {
1019
+ super();
1020
+ this.render = this.render.bind(this);
1021
+ }
1022
+
1023
+ connectedCallback() {
1024
+ this.render();
1025
+ }
1026
+
1027
+ attributeChangedCallback() {
1028
+ this.render();
1029
+ }
1030
+
1031
+ get centered() {
1032
+ return this.getAttribute('centered') || 'h1';
1033
+ }
1034
+
1035
+ set centered(val) {
1036
+ this.setAttribute('centered', val);
1037
+ }
1038
+
1039
+ get space() {
1040
+ return this.getAttribute('space') || 'var(--s1)';
1041
+ }
1042
+
1043
+ set space(val) {
1044
+ this.setAttribute('space', val);
1045
+ }
1046
+
1047
+ get minHeight() {
1048
+ return this.getAttribute('min-height') || '100vh';
1049
+ }
1050
+
1051
+ set minHeight(val) {
1052
+ this.setAttribute('min-height', val);
1053
+ }
1054
+
1055
+ get noPad() {
1056
+ return this.hasAttribute('no-pad');
1057
+ }
1058
+
1059
+ set noPad(val) {
1060
+ if (val) {
1061
+ this.setAttribute('no-pad', '');
1062
+ } else {
1063
+ this.removeAttribute('no-pad');
1064
+ }
1065
+ }
1066
+
1067
+ render() {
1068
+ this.style.setProperty('--esgal-min-height', this.minHeight);
1069
+ this.style.setProperty('--esgal-space', this.space);
1070
+
1071
+ const noPadStr = this.noPad ? '-noPad' : '';
1072
+ const id = `Esgal-${this.centered}-${this.minHeight}-${this.space}${noPadStr}`.replace(/[^\w-]/g, '');
1073
+ this.dataset.i = id;
1074
+
1075
+ if (!document.getElementById(id)) {
1076
+ const styleEl = document.createElement('style');
1077
+ styleEl.id = id;
1078
+
1079
+ const selector = `[data-i="${id}"]`;
1080
+ const padding = this.noPad ? '0' : this.space;
1081
+
1082
+ styleEl.textContent = `
1083
+ ${selector} {
1084
+ min-block-size: ${this.minHeight};
1085
+ padding: ${padding};
1086
+ }
1087
+ ${selector} > * {
1088
+ margin-block: ${this.space};
1089
+ }
1090
+ ${selector} > :first-child:not(${this.centered}) {
1091
+ margin-block-start: 0;
1092
+ }
1093
+ ${selector} > :last-child:not(${this.centered}) {
1094
+ margin-block-end: 0;
1095
+ }
1096
+ ${selector} > ${this.centered} {
1097
+ margin-block: auto;
1098
+ }
1099
+ `;
1100
+ document.head.appendChild(styleEl);
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ if ('customElements' in window) {
1106
+ customElements.define('i-esgal', EsgalLayout);
1107
+ }
1108
+
1109
+
1110
+
1111
+
1112
+ // vircantie
1113
+ /**
1114
+ * Vircantie Layout Custom Element
1115
+ *
1116
+ * Auto-flowing responsive grid with minimum column width.
1117
+ *
1118
+ * @property {string} min - Minimum column width (default: 250px)
1119
+ * @property {string} space - Gap between grid cells (default: var(--s1))
1120
+ *
1121
+ * @example
1122
+ * <i-vircantie min="300px" space="var(--s2)">
1123
+ * <div>Card 1</div>
1124
+ * <div>Card 2</div>
1125
+ * <div>Card 3</div>
1126
+ * </i-vircantie>
1127
+ */
1128
+
1129
+ class VircantieLayout extends HTMLElement {
1130
+ static get observedAttributes() {
1131
+ return ['min', 'space'];
1132
+ }
1133
+
1134
+ constructor() {
1135
+ super();
1136
+ this.render = this.render.bind(this);
1137
+ }
1138
+
1139
+ connectedCallback() {
1140
+ this.render();
1141
+ }
1142
+
1143
+ attributeChangedCallback() {
1144
+ this.render();
1145
+ }
1146
+
1147
+ get min() {
1148
+ return this.getAttribute('min') || '250px';
1149
+ }
1150
+
1151
+ set min(val) {
1152
+ this.setAttribute('min', val);
1153
+ }
1154
+
1155
+ get space() {
1156
+ return this.getAttribute('space') || 'var(--s1)';
1157
+ }
1158
+
1159
+ set space(val) {
1160
+ this.setAttribute('space', val);
1161
+ }
1162
+
1163
+ render() {
1164
+ this.style.setProperty('--vircantie-min', this.min);
1165
+ this.style.setProperty('--vircantie-space', this.space);
1166
+
1167
+ const id = `Vircantie-${this.min}-${this.space}`.replace(/[^\w-]/g, '');
1168
+ this.dataset.i = id;
1169
+
1170
+ if (!document.getElementById(id)) {
1171
+ const styleEl = document.createElement('style');
1172
+ styleEl.id = id;
1173
+
1174
+ const selector = `[data-i="${id}"]`;
1175
+
1176
+ styleEl.textContent = `
1177
+ ${selector} {
1178
+ gap: ${this.space};
1179
+ }
1180
+ @supports (width: min(${this.min}, 100%)) {
1181
+ ${selector} {
1182
+ vircantie-template-columns: repeat(auto-fit, minmax(min(${this.min}, 100%), 1fr));
1183
+ }
1184
+ }
1185
+ `;
1186
+ document.head.appendChild(styleEl);
1187
+ }
1188
+ }
1189
+ }
1190
+
1191
+ if ('customElements' in window) {
1192
+ customElements.define('i-vircantie', VircantieLayout);
1193
+ }
1194
+
1195
+
1196
+
1197
+
1198
+ // gant-thala
1199
+ /**
1200
+ * Frame Layout Custom Element
1201
+ *
1202
+ * Constrains content to a specific aspect ratio.
1203
+ *
1204
+ * @property {string} ratio - GantThala ratio as "n:d" (default: 16:9)
1205
+ *
1206
+ * @example
1207
+ * <i-gant-thala ratio="4:3">
1208
+ * <img src="photo.jpg" alt="A photo">
1209
+ * </i-gant-thala>
1210
+ */
1211
+
1212
+ class GantThalaLayout extends HTMLElement {
1213
+ static get observedAttributes() {
1214
+ return ['ratio'];
1215
+ }
1216
+
1217
+ constructor() {
1218
+ super();
1219
+ this.render = this.render.bind(this);
1220
+ }
1221
+
1222
+ connectedCallback() {
1223
+ this.render();
1224
+ }
1225
+
1226
+ attributeChangedCallback() {
1227
+ this.render();
1228
+ }
1229
+
1230
+ get ratio() {
1231
+ return this.getAttribute('ratio') || '16:9';
1232
+ }
1233
+
1234
+ set ratio(val) {
1235
+ this.setAttribute('ratio', val);
1236
+ }
1237
+
1238
+ render() {
1239
+ const [n, d] = this.ratio.split(':').map(v => v.trim());
1240
+
1241
+ this.style.setProperty('--gant-thala-n', n);
1242
+ this.style.setProperty('--gant-thala-d', d);
1243
+
1244
+ const id = `GantThala-${n}-${d}`.replace(/[^\w-]/g, '');
1245
+ this.dataset.i = id;
1246
+
1247
+ if (!document.getElementById(id)) {
1248
+ const styleEl = document.createElement('style');
1249
+ styleEl.id = id;
1250
+
1251
+ const selector = `[data-i="${id}"]`;
1252
+
1253
+ styleEl.textContent = `
1254
+ ${selector} {
1255
+ gant-thala-ratio: ${n} / ${d};
1256
+ }
1257
+ `;
1258
+ document.head.appendChild(styleEl);
1259
+ }
1260
+ }
1261
+ }
1262
+
1263
+ if ('customElements' in window) {
1264
+ customElements.define('i-gant-thala', GantThalaLayout);
1265
+ }
1266
+
1267
+
1268
+
1269
+
1270
+ // glan-tholl
1271
+ /**
1272
+ * Reel Layout Custom Element
1273
+ *
1274
+ * Horizontal scrolling container with native browser scrolling.
1275
+ * Uses ResizeObserver and MutationObserver for overflow detection.
1276
+ *
1277
+ * @property {string} itemWidth - Width of each item (default: auto)
1278
+ * @property {string} space - Gap between items (default: var(--s1))
1279
+ * @property {string} height - Height of the reel (default: auto)
1280
+ * @property {boolean} noBar - Hide the scrollbar
1281
+ *
1282
+ * @example
1283
+ * <i-glan-tholl item-width="300px" space="var(--s2)">
1284
+ * <div>Card 1</div>
1285
+ * <div>Card 2</div>
1286
+ * <div>Card 3</div>
1287
+ * </i-glan-tholl>
1288
+ */
1289
+
1290
+ class GlanThollLayout extends HTMLElement {
1291
+ static get observedAttributes() {
1292
+ return ['item-width', 'space', 'height', 'no-bar'];
1293
+ }
1294
+
1295
+ constructor() {
1296
+ super();
1297
+ this.render = this.render.bind(this);
1298
+ this.toggleOverflowClass = this.toggleOverflowClass.bind(this);
1299
+ this.resizeObserver = null;
1300
+ this.mutationObserver = null;
1301
+ }
1302
+
1303
+ connectedCallback() {
1304
+ this.render();
1305
+ this.setupObservers();
1306
+ }
1307
+
1308
+ disconnectedCallback() {
1309
+ if (this.resizeObserver) {
1310
+ this.resizeObserver.disconnect();
1311
+ }
1312
+ if (this.mutationObserver) {
1313
+ this.mutationObserver.disconnect();
1314
+ }
1315
+ }
1316
+
1317
+ attributeChangedCallback() {
1318
+ this.render();
1319
+ }
1320
+
1321
+ get itemWidth() {
1322
+ return this.getAttribute('item-width') || 'auto';
1323
+ }
1324
+
1325
+ set itemWidth(val) {
1326
+ this.setAttribute('item-width', val);
1327
+ }
1328
+
1329
+ get space() {
1330
+ return this.getAttribute('space') || 'var(--s1)';
1331
+ }
1332
+
1333
+ set space(val) {
1334
+ this.setAttribute('space', val);
1335
+ }
1336
+
1337
+ get height() {
1338
+ return this.getAttribute('height') || 'auto';
1339
+ }
1340
+
1341
+ set height(val) {
1342
+ this.setAttribute('height', val);
1343
+ }
1344
+
1345
+ get noBar() {
1346
+ return this.hasAttribute('no-bar');
1347
+ }
1348
+
1349
+ set noBar(val) {
1350
+ if (val) {
1351
+ this.setAttribute('no-bar', '');
1352
+ } else {
1353
+ this.removeAttribute('no-bar');
1354
+ }
1355
+ }
1356
+
1357
+ toggleOverflowClass() {
1358
+ this.classList.toggle('overflowing', this.scrollWidth > this.clientWidth);
1359
+ }
1360
+
1361
+ setupObservers() {
1362
+ // ResizeObserver for container size changes
1363
+ if ('ResizeObserver' in window) {
1364
+ this.resizeObserver = new ResizeObserver(entries => {
1365
+ this.toggleOverflowClass();
1366
+ });
1367
+ this.resizeObserver.observe(this);
1368
+ }
1369
+
1370
+ // MutationObserver for child changes
1371
+ if ('MutationObserver' in window) {
1372
+ this.mutationObserver = new MutationObserver(entries => {
1373
+ this.toggleOverflowClass();
1374
+ });
1375
+ this.mutationObserver.observe(this, { childList: true });
1376
+ }
1377
+
1378
+ // Initial check
1379
+ this.toggleOverflowClass();
1380
+ }
1381
+
1382
+ render() {
1383
+ this.style.setProperty('--glan-tholl-item-width', this.itemWidth);
1384
+ this.style.setProperty('--glan-tholl-space', this.space);
1385
+ this.style.setProperty('--glan-tholl-height', this.height);
1386
+
1387
+ const noBarStr = this.noBar ? '-noBar' : '';
1388
+ const id = `GlanTholl-${this.itemWidth}-${this.space}-${this.height}${noBarStr}`.replace(/[^\w-]/g, '');
1389
+ this.dataset.i = id;
1390
+
1391
+ if (!document.getElementById(id)) {
1392
+ const styleEl = document.createElement('style');
1393
+ styleEl.id = id;
1394
+
1395
+ const selector = `[data-i="${id}"]`;
1396
+
1397
+ styleEl.textContent = `
1398
+ ${selector} {
1399
+ block-size: ${this.height};
1400
+ }
1401
+ ${selector} > * {
1402
+ flex: 0 0 ${this.itemWidth};
1403
+ }
1404
+ ${selector} > * + * {
1405
+ margin-inline-start: ${this.space};
1406
+ }
1407
+ `;
1408
+ document.head.appendChild(styleEl);
1409
+ }
1410
+ }
1411
+ }
1412
+
1413
+ if ('customElements' in window) {
1414
+ customElements.define('i-glan-tholl', GlanThollLayout);
1415
+ }
1416
+
1417
+
1418
+
1419
+
1420
+ // fano
1421
+ /**
1422
+ * Imposter Layout Custom Element
1423
+ *
1424
+ * Positions an element centered over a positioning container.
1425
+ *
1426
+ * @property {boolean} fixed - Use fixed positioning (viewport-relative)
1427
+ * @property {boolean} contain - Prevent overflow outside container
1428
+ * @property {string} margin - Minimum gap from container edges (when contained)
1429
+ *
1430
+ * @example
1431
+ * <div style="position: relative;">
1432
+ * <p>Background content</p>
1433
+ * <i-fano contain margin="var(--s1)">
1434
+ * <dialog open>Modal content</dialog>
1435
+ * </i-fano>
1436
+ * </div>
1437
+ */
1438
+
1439
+ class FanoLayout extends HTMLElement {
1440
+ static get observedAttributes() {
1441
+ return ['fixed', 'contain', 'margin'];
1442
+ }
1443
+
1444
+ constructor() {
1445
+ super();
1446
+ this.render = this.render.bind(this);
1447
+ }
1448
+
1449
+ connectedCallback() {
1450
+ this.render();
1451
+ }
1452
+
1453
+ attributeChangedCallback() {
1454
+ this.render();
1455
+ }
1456
+
1457
+ get fixed() {
1458
+ return this.hasAttribute('fixed');
1459
+ }
1460
+
1461
+ set fixed(val) {
1462
+ if (val) {
1463
+ this.setAttribute('fixed', '');
1464
+ } else {
1465
+ this.removeAttribute('fixed');
1466
+ }
1467
+ }
1468
+
1469
+ get contain() {
1470
+ return this.hasAttribute('contain');
1471
+ }
1472
+
1473
+ set contain(val) {
1474
+ if (val) {
1475
+ this.setAttribute('contain', '');
1476
+ } else {
1477
+ this.removeAttribute('contain');
1478
+ }
1479
+ }
1480
+
1481
+ get margin() {
1482
+ return this.getAttribute('margin') || '0px';
1483
+ }
1484
+
1485
+ set margin(val) {
1486
+ this.setAttribute('margin', val);
1487
+ }
1488
+
1489
+ render() {
1490
+ this.style.setProperty('--fano-margin', this.margin);
1491
+
1492
+ const fixedStr = this.fixed ? '-fixed' : '';
1493
+ const containStr = this.contain ? '-contain' : '';
1494
+ const id = `Fano-${this.margin}${fixedStr}${containStr}`.replace(/[^\w-]/g, '');
1495
+ this.dataset.i = id;
1496
+
1497
+ if (!document.getElementById(id)) {
1498
+ const styleEl = document.createElement('style');
1499
+ styleEl.id = id;
1500
+
1501
+ const selector = `[data-i="${id}"]`;
1502
+ let css = '';
1503
+
1504
+ if (this.fixed) {
1505
+ css += `${selector} { position: fixed; }`;
1506
+ }
1507
+
1508
+ if (this.contain) {
1509
+ css += `
1510
+ ${selector} {
1511
+ overflow: auto;
1512
+ max-inline-size: calc(100% - (${this.margin} * 2));
1513
+ max-block-size: calc(100% - (${this.margin} * 2));
1514
+ }
1515
+ `;
1516
+ }
1517
+
1518
+ styleEl.textContent = css;
1519
+ document.head.appendChild(styleEl);
1520
+ }
1521
+ }
1522
+ }
1523
+
1524
+ if ('customElements' in window) {
1525
+ customElements.define('i-fano', FanoLayout);
1526
+ }
1527
+
1528
+
1529
+
1530
+
1531
+ // thann
1532
+ /**
1533
+ * Thann Layout Custom Element
1534
+ *
1535
+ * Aligns an SVG icon with accompanying text.
1536
+ *
1537
+ * @property {string} space - Gap between icon and text (default: natural word space)
1538
+ * @property {string} label - Accessible label for standalone icons
1539
+ *
1540
+ * @example
1541
+ * <i-thann space="0.5em">
1542
+ * <svg>...</svg>
1543
+ * Close
1544
+ * </i-thann>
1545
+ */
1546
+
1547
+ class ThannLayout extends HTMLElement {
1548
+ static get observedAttributes() {
1549
+ return ['space', 'label'];
1550
+ }
1551
+
1552
+ constructor() {
1553
+ super();
1554
+ this.render = this.render.bind(this);
1555
+ }
1556
+
1557
+ connectedCallback() {
1558
+ this.render();
1559
+ }
1560
+
1561
+ attributeChangedCallback() {
1562
+ this.render();
1563
+ }
1564
+
1565
+ get space() {
1566
+ return this.getAttribute('space');
1567
+ }
1568
+
1569
+ set space(val) {
1570
+ if (val) {
1571
+ this.setAttribute('space', val);
1572
+ } else {
1573
+ this.removeAttribute('space');
1574
+ }
1575
+ }
1576
+
1577
+ get label() {
1578
+ return this.getAttribute('label');
1579
+ }
1580
+
1581
+ set label(val) {
1582
+ if (val) {
1583
+ this.setAttribute('label', val);
1584
+ } else {
1585
+ this.removeAttribute('label');
1586
+ }
1587
+ }
1588
+
1589
+ render() {
1590
+ // Handle accessibility for standalone icons
1591
+ if (this.label) {
1592
+ this.setAttribute('role', 'img');
1593
+ this.setAttribute('aria-label', this.label);
1594
+ } else {
1595
+ this.removeAttribute('role');
1596
+ this.removeAttribute('aria-label');
1597
+ }
1598
+
1599
+ // Only set custom property if space is specified
1600
+ if (this.space) {
1601
+ this.style.setProperty('--thann-space', this.space);
1602
+
1603
+ const id = `Thann-${this.space}`.replace(/[^\w-]/g, '');
1604
+ this.dataset.i = id;
1605
+
1606
+ if (!document.getElementById(id)) {
1607
+ const styleEl = document.createElement('style');
1608
+ styleEl.id = id;
1609
+
1610
+ const selector = `[data-i="${id}"]`;
1611
+
1612
+ styleEl.textContent = `
1613
+ ${selector} {
1614
+ gap: ${this.space};
1615
+ }
1616
+ `;
1617
+ document.head.appendChild(styleEl);
1618
+ }
1619
+ }
1620
+ }
1621
+ }
1622
+
1623
+ if ('customElements' in window) {
1624
+ customElements.define('i-thann', ThannLayout);
1625
+ }
1626
+
1627
+
1628
+
1629
+
1630
+ // adleithian
1631
+ /**
1632
+ * Adleithian Layout Custom Element
1633
+ *
1634
+ * Establishes a container query context.
1635
+ *
1636
+ * @property {string} name - Adleithian name for targeted queries (optional)
1637
+ *
1638
+ * @example
1639
+ * <i-adleithian name="card">
1640
+ * <div class="card">...</div>
1641
+ * </i-adleithian>
1642
+ *
1643
+ * CSS:
1644
+ * @container card (width < 300px) {
1645
+ * .card { flex-direction: column; }
1646
+ * }
1647
+ */
1648
+
1649
+ class AdleithianLayout extends HTMLElement {
1650
+ static get observedAttributes() {
1651
+ return ['name'];
1652
+ }
1653
+
1654
+ constructor() {
1655
+ super();
1656
+ this.render = this.render.bind(this);
1657
+ }
1658
+
1659
+ connectedCallback() {
1660
+ this.render();
1661
+ }
1662
+
1663
+ attributeChangedCallback() {
1664
+ this.render();
1665
+ }
1666
+
1667
+ get name() {
1668
+ return this.getAttribute('name');
1669
+ }
1670
+
1671
+ set name(val) {
1672
+ if (val) {
1673
+ this.setAttribute('name', val);
1674
+ } else {
1675
+ this.removeAttribute('name');
1676
+ }
1677
+ }
1678
+
1679
+ render() {
1680
+ // Set adleithian-name via style if provided
1681
+ if (this.name) {
1682
+ this.style.containerName = this.name;
1683
+ } else {
1684
+ this.style.containerName = '';
1685
+ }
1686
+ }
1687
+ }
1688
+
1689
+ if ('customElements' in window) {
1690
+ customElements.define('i-adleithian', AdleithianLayout);
1691
+ }
1692
+
1693
+
1694
+
1695
+
1696
+ // him
1697
+ /**
1698
+ * Him Layout Custom Element (NEW)
1699
+ *
1700
+ * Creates sticky positioning with configurable offset.
1701
+ * Optionally detects "stuck" state via IntersectionObserver.
1702
+ *
1703
+ * @property {string} to - Direction to stick: top, bottom, left, right (default: top)
1704
+ * @property {string} offset - Distance from edge when stuck (default: 0)
1705
+ * @property {boolean} sentinel - Enable stuck state detection
1706
+ *
1707
+ * @example
1708
+ * <i-him offset="var(--s1)" sentinel>
1709
+ * <header>This header sticks</header>
1710
+ * </i-him>
1711
+ */
1712
+
1713
+ class HimLayout extends HTMLElement {
1714
+ static get observedAttributes() {
1715
+ return ['to', 'offset', 'sentinel'];
1716
+ }
1717
+
1718
+ constructor() {
1719
+ super();
1720
+ this.render = this.render.bind(this);
1721
+ this.observer = null;
1722
+ this.sentinelEl = null;
1723
+ }
1724
+
1725
+ connectedCallback() {
1726
+ this.render();
1727
+ }
1728
+
1729
+ disconnectedCallback() {
1730
+ this.cleanupSentinel();
1731
+ }
1732
+
1733
+ attributeChangedCallback() {
1734
+ this.render();
1735
+ }
1736
+
1737
+ get to() {
1738
+ return this.getAttribute('to') || 'top';
1739
+ }
1740
+
1741
+ set to(val) {
1742
+ this.setAttribute('to', val);
1743
+ }
1744
+
1745
+ get offset() {
1746
+ return this.getAttribute('offset') || '0';
1747
+ }
1748
+
1749
+ set offset(val) {
1750
+ this.setAttribute('offset', val);
1751
+ }
1752
+
1753
+ get sentinel() {
1754
+ return this.hasAttribute('sentinel');
1755
+ }
1756
+
1757
+ set sentinel(val) {
1758
+ if (val) {
1759
+ this.setAttribute('sentinel', '');
1760
+ } else {
1761
+ this.removeAttribute('sentinel');
1762
+ }
1763
+ }
1764
+
1765
+ cleanupSentinel() {
1766
+ if (this.observer) {
1767
+ this.observer.disconnect();
1768
+ this.observer = null;
1769
+ }
1770
+ if (this.sentinelEl) {
1771
+ this.sentinelEl.remove();
1772
+ this.sentinelEl = null;
1773
+ }
1774
+ }
1775
+
1776
+ setupSentinel() {
1777
+ this.cleanupSentinel();
1778
+
1779
+ if (!this.sentinel || !('IntersectionObserver' in window)) {
1780
+ return;
1781
+ }
1782
+
1783
+ // Create invisible sentinel element
1784
+ this.sentinelEl = document.createElement('div');
1785
+ this.sentinelEl.style.cssText = `
1786
+ position: absolute;
1787
+ height: 1px;
1788
+ width: 100%;
1789
+ pointer-events: none;
1790
+ visibility: hidden;
1791
+ `;
1792
+
1793
+ // Position sentinel based on sticky direction
1794
+ if (this.to === 'bottom') {
1795
+ this.sentinelEl.style.bottom = '0';
1796
+ this.insertAdjacentElement('afterend', this.sentinelEl);
1797
+ } else {
1798
+ this.sentinelEl.style.top = '0';
1799
+ this.insertAdjacentElement('beforebegin', this.sentinelEl);
1800
+ }
1801
+
1802
+ // Observe the sentinel
1803
+ this.observer = new IntersectionObserver(
1804
+ (entries) => {
1805
+ entries.forEach((entry) => {
1806
+ // When sentinel is not visible, we're "stuck"
1807
+ const isStuck = !entry.isIntersecting;
1808
+ this.dataset.stuck = isStuck;
1809
+ this.dispatchEvent(new CustomEvent('stuck-change', {
1810
+ detail: { stuck: isStuck }
1811
+ }));
1812
+ });
1813
+ },
1814
+ { threshold: [0] }
1815
+ );
1816
+
1817
+ this.observer.observe(this.sentinelEl);
1818
+ }
1819
+
1820
+ render() {
1821
+ this.style.setProperty('--him-offset', this.offset);
1822
+
1823
+ const id = `Him-${this.to}-${this.offset}`.replace(/[^\w-]/g, '');
1824
+ this.dataset.i = id;
1825
+
1826
+ if (!document.getElementById(id)) {
1827
+ const styleEl = document.createElement('style');
1828
+ styleEl.id = id;
1829
+
1830
+ const selector = `[data-i="${id}"]`;
1831
+ let css = '';
1832
+
1833
+ switch (this.to) {
1834
+ case 'bottom':
1835
+ css = `${selector} { inset-block-start: auto; inset-block-end: ${this.offset}; }`;
1836
+ break;
1837
+ case 'left':
1838
+ css = `${selector} { inset-block-start: auto; inset-inline-start: ${this.offset}; }`;
1839
+ break;
1840
+ case 'right':
1841
+ css = `${selector} { inset-block-start: auto; inset-inline-end: ${this.offset}; }`;
1842
+ break;
1843
+ default: // top
1844
+ css = `${selector} { inset-block-start: ${this.offset}; }`;
1845
+ }
1846
+
1847
+ styleEl.textContent = css;
1848
+ document.head.appendChild(styleEl);
1849
+ }
1850
+
1851
+ // Setup or cleanup sentinel observation
1852
+ if (this.sentinel) {
1853
+ // Defer to ensure element is in DOM
1854
+ requestAnimationFrame(() => this.setupSentinel());
1855
+ } else {
1856
+ this.cleanupSentinel();
1857
+ }
1858
+ }
1859
+ }
1860
+
1861
+ if ('customElements' in window) {
1862
+ customElements.define('i-him', HimLayout);
1863
+ }
1864
+
1865
+
1866
+
1867
+
1868
+ // miriant
1869
+ /**
1870
+ * Grid-Placed Layout Custom Element (NEW)
1871
+ *
1872
+ * A CSS Grid with explicit control over columns, rows, and item placement.
1873
+ *
1874
+ * @property {number} columns - Number of grid columns (default: 12)
1875
+ * @property {string} space - Gap between grid cells (default: var(--s1))
1876
+ * @property {string} rowHeight - Height of auto-generated rows (default: minmax(0, auto))
1877
+ * @property {boolean} dense - Enable dense packing to fill holes
1878
+ *
1879
+ * Children can use data attributes for placement:
1880
+ * - data-col-span="N" - Span N columns (1-12 or "full")
1881
+ * - data-row-span="N" - Span N rows (1-6)
1882
+ * - data-col-start="N" - Start at column N
1883
+ * - data-row-start="N" - Start at row N
1884
+ *
1885
+ * @example
1886
+ * <i-miriant columns="4" space="var(--s2)">
1887
+ * <div data-col-span="2" data-row-span="2">Feature</div>
1888
+ * <div>Small</div>
1889
+ * <div>Small</div>
1890
+ * <div data-col-span="full">Full width</div>
1891
+ * </i-miriant>
1892
+ */
1893
+
1894
+ class MiriantLayout extends HTMLElement {
1895
+ static get observedAttributes() {
1896
+ return ['columns', 'space', 'row-height', 'dense'];
1897
+ }
1898
+
1899
+ constructor() {
1900
+ super();
1901
+ this.render = this.render.bind(this);
1902
+ this.mutationObserver = null;
1903
+ }
1904
+
1905
+ connectedCallback() {
1906
+ this.render();
1907
+ this.setupChildObserver();
1908
+ }
1909
+
1910
+ disconnectedCallback() {
1911
+ if (this.mutationObserver) {
1912
+ this.mutationObserver.disconnect();
1913
+ }
1914
+ }
1915
+
1916
+ attributeChangedCallback() {
1917
+ this.render();
1918
+ }
1919
+
1920
+ get columns() {
1921
+ return parseInt(this.getAttribute('columns'), 10) || 12;
1922
+ }
1923
+
1924
+ set columns(val) {
1925
+ this.setAttribute('columns', val);
1926
+ }
1927
+
1928
+ get space() {
1929
+ return this.getAttribute('space') || 'var(--s1)';
1930
+ }
1931
+
1932
+ set space(val) {
1933
+ this.setAttribute('space', val);
1934
+ }
1935
+
1936
+ get rowHeight() {
1937
+ return this.getAttribute('row-height') || 'minmax(0, auto)';
1938
+ }
1939
+
1940
+ set rowHeight(val) {
1941
+ this.setAttribute('row-height', val);
1942
+ }
1943
+
1944
+ get dense() {
1945
+ return this.hasAttribute('dense');
1946
+ }
1947
+
1948
+ set dense(val) {
1949
+ if (val) {
1950
+ this.setAttribute('dense', '');
1951
+ } else {
1952
+ this.removeAttribute('dense');
1953
+ }
1954
+ }
1955
+
1956
+ setupChildObserver() {
1957
+ if (!('MutationObserver' in window)) return;
1958
+
1959
+ this.mutationObserver = new MutationObserver((mutations) => {
1960
+ mutations.forEach((mutation) => {
1961
+ if (mutation.type === 'childList') {
1962
+ this.styleChildren();
1963
+ }
1964
+ });
1965
+ });
1966
+
1967
+ this.mutationObserver.observe(this, { childList: true });
1968
+ this.styleChildren();
1969
+ }
1970
+
1971
+ styleChildren() {
1972
+ // Apply inline styles for explicit placement (data-col-start, data-row-start)
1973
+ Array.from(this.children).forEach((child) => {
1974
+ const colStart = child.dataset.colStart;
1975
+ const rowStart = child.dataset.rowStart;
1976
+
1977
+ if (colStart) {
1978
+ child.style.gridColumnStart = colStart;
1979
+ }
1980
+ if (rowStart) {
1981
+ child.style.gridRowStart = rowStart;
1982
+ }
1983
+ });
1984
+ }
1985
+
1986
+ render() {
1987
+ this.style.setProperty('--miriant-columns', this.columns);
1988
+ this.style.setProperty('--miriant-space', this.space);
1989
+ this.style.setProperty('--miriant-row-height', this.rowHeight);
1990
+
1991
+ const denseStr = this.dense ? '-dense' : '';
1992
+ const id = `Miriant-${this.columns}-${this.space}-${this.rowHeight}${denseStr}`.replace(/[^\w-]/g, '');
1993
+ this.dataset.i = id;
1994
+
1995
+ // Set container-type for container queries
1996
+ this.style.containerType = 'inline-size';
1997
+
1998
+ if (!document.getElementById(id)) {
1999
+ const styleEl = document.createElement('style');
2000
+ styleEl.id = id;
2001
+
2002
+ const selector = `[data-i="${id}"]`;
2003
+
2004
+ let css = `
2005
+ ${selector} {
2006
+ grid-template-columns: repeat(${this.columns}, 1fr);
2007
+ gap: ${this.space};
2008
+ grid-auto-rows: ${this.rowHeight};
2009
+ }
2010
+ `;
2011
+
2012
+ if (this.dense) {
2013
+ css += `${selector} { grid-auto-flow: dense; }`;
2014
+ }
2015
+
2016
+ styleEl.textContent = css;
2017
+ document.head.appendChild(styleEl);
2018
+ }
2019
+ }
2020
+ }
2021
+
2022
+ if ('customElements' in window) {
2023
+ customElements.define('i-miriant', MiriantLayout);
2024
+ }
2025
+
2026
+
2027
+
2028
+
2029
+ // gonath
2030
+ /**
2031
+ * Gonath Layout Custom Element (NEW)
2032
+ *
2033
+ * Pinterest-style layout with efficient column packing.
2034
+ *
2035
+ * Uses CSS columns (widely supported) with progressive enhancement
2036
+ * to CSS Grid masonry where available.
2037
+ *
2038
+ * @property {number} columns - Number of columns (default: 3)
2039
+ * @property {string} space - Gap between items (default: var(--s1))
2040
+ *
2041
+ * NOTE: CSS columns flow top-to-bottom, then left-to-right.
2042
+ * This means visual order differs from DOM order.
2043
+ * For true masonry ordering, JavaScript is required.
2044
+ *
2045
+ * @example
2046
+ * <i-gonath columns="4" space="var(--s2)">
2047
+ * <div>Item 1</div>
2048
+ * <div>Taller Item 2</div>
2049
+ * <div>Item 3</div>
2050
+ * ...
2051
+ * </i-gonath>
2052
+ */
2053
+
2054
+ class GonathLayout extends HTMLElement {
2055
+ static get observedAttributes() {
2056
+ return ['columns', 'space'];
2057
+ }
2058
+
2059
+ constructor() {
2060
+ super();
2061
+ this.render = this.render.bind(this);
2062
+ }
2063
+
2064
+ connectedCallback() {
2065
+ this.render();
2066
+ }
2067
+
2068
+ attributeChangedCallback() {
2069
+ this.render();
2070
+ }
2071
+
2072
+ get columns() {
2073
+ return parseInt(this.getAttribute('columns'), 10) || 3;
2074
+ }
2075
+
2076
+ set columns(val) {
2077
+ this.setAttribute('columns', val);
2078
+ }
2079
+
2080
+ get space() {
2081
+ return this.getAttribute('space') || 'var(--s1)';
2082
+ }
2083
+
2084
+ set space(val) {
2085
+ this.setAttribute('space', val);
2086
+ }
2087
+
2088
+ // Check if CSS Grid masonry is supported
2089
+ static get isGridGonathSupported() {
2090
+ return CSS.supports('grid-template-rows', 'masonry');
2091
+ }
2092
+
2093
+ render() {
2094
+ this.style.setProperty('--gonath-columns', this.columns);
2095
+ this.style.setProperty('--gonath-space', this.space);
2096
+
2097
+ const id = `Gonath-${this.columns}-${this.space}`.replace(/[^\w-]/g, '');
2098
+ this.dataset.i = id;
2099
+
2100
+ if (!document.getElementById(id)) {
2101
+ const styleEl = document.createElement('style');
2102
+ styleEl.id = id;
2103
+
2104
+ const selector = `[data-i="${id}"]`;
2105
+
2106
+ let css = `
2107
+ ${selector} {
2108
+ column-count: ${this.columns};
2109
+ column-gap: ${this.space};
2110
+ }
2111
+ ${selector} > * {
2112
+ margin-block-end: ${this.space};
2113
+ }
2114
+ `;
2115
+
2116
+ // Progressive enhancement for CSS Grid masonry
2117
+ css += `
2118
+ @supports (grid-template-rows: masonry) {
2119
+ ${selector} {
2120
+ display: grid;
2121
+ column-count: unset;
2122
+ grid-template-columns: repeat(${this.columns}, 1fr);
2123
+ grid-template-rows: masonry;
2124
+ gap: ${this.space};
2125
+ }
2126
+ ${selector} > * {
2127
+ margin-block-end: 0;
2128
+ }
2129
+ }
2130
+ `;
2131
+
2132
+ styleEl.textContent = css;
2133
+ document.head.appendChild(styleEl);
2134
+ }
2135
+ }
2136
+ }
2137
+
2138
+ if ('customElements' in window) {
2139
+ customElements.define('i-gonath', GonathLayout);
2140
+ }
2141
+
2142
+
2143
+
2144
+
2145
+
2146
+
2147
+ // Exports
2148
+ exports.HathLayout = HathLayout;
2149
+ exports.BauLayout = BauLayout;
2150
+ exports.EnedhLayout = EnedhLayout;
2151
+ exports.TiniathLayout = TiniathLayout;
2152
+ exports.GlanVelegLayout = GlanVelegLayout;
2153
+ exports.GwistindorLayout = GwistindorLayout;
2154
+ exports.EsgalLayout = EsgalLayout;
2155
+ exports.VircantieLayout = VircantieLayout;
2156
+ exports.GantThalaLayout = GantThalaLayout;
2157
+ exports.GlanThollLayout = GlanThollLayout;
2158
+ exports.FanoLayout = FanoLayout;
2159
+ exports.ThannLayout = ThannLayout;
2160
+ exports.AdleithianLayout = AdleithianLayout;
2161
+ exports.HimLayout = HimLayout;
2162
+ exports.MiriantLayout = MiriantLayout;
2163
+ exports.GonathLayout = GonathLayout;
2164
+ exports.transition = transition;
2165
+ exports.transitionTo = transitionTo;
2166
+ exports.transitionTheme = transitionTheme;
2167
+ exports.transitionRatio = transitionRatio;
2168
+ exports.transitionLayout = transitionLayout;
2169
+ exports.supportsViewTransitions = supportsViewTransitions;
2170
+ exports.VERSION = '2.0.0';
2171
+
2172
+ Object.defineProperty(exports, '__esModule', { value: true });
2173
+ });