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.
- package/LICENSE +21 -0
- package/README.md +518 -0
- package/dist/elvish.css +2194 -0
- package/dist/elvish.d.ts +78 -0
- package/dist/elvish.esm.js +2185 -0
- package/dist/elvish.iife.js +2169 -0
- package/dist/elvish.min.css +9 -0
- package/dist/elvish.umd.js +2173 -0
- package/elvish.css +28 -0
- package/elvish.js +81 -0
- package/global/global.css +16 -0
- package/global/modern.css +305 -0
- package/global/reset.css +507 -0
- package/global/tokens.css +154 -0
- package/global/transitions.css +288 -0
- package/global/transitions.js +289 -0
- package/global/utilities.css +151 -0
- package/package.json +61 -0
- package/primitives/adleithian/adleithian.css +16 -0
- package/primitives/adleithian/adleithian.js +63 -0
- package/primitives/bau/bau.css +86 -0
- package/primitives/bau/bau.js +127 -0
- package/primitives/enedh/enedh.css +38 -0
- package/primitives/enedh/enedh.js +110 -0
- package/primitives/esgal/esgal.css +39 -0
- package/primitives/esgal/esgal.js +115 -0
- package/primitives/fano/fano.css +28 -0
- package/primitives/fano/fano.js +108 -0
- package/primitives/gant-thala/gant-thala.css +32 -0
- package/primitives/gant-thala/gant-thala.js +69 -0
- package/primitives/glan-tholl/glan-tholl.css +71 -0
- package/primitives/glan-tholl/glan-tholl.js +147 -0
- package/primitives/glan-veleg/glan-veleg.css +45 -0
- package/primitives/glan-veleg/glan-veleg.js +138 -0
- package/primitives/gonath/gonath.css +57 -0
- package/primitives/gonath/gonath.js +113 -0
- package/primitives/gwistindor/gwistindor.css +52 -0
- package/primitives/gwistindor/gwistindor.js +96 -0
- package/primitives/hath/hath.css +39 -0
- package/primitives/hath/hath.js +107 -0
- package/primitives/him/him.css +43 -0
- package/primitives/him/him.js +169 -0
- package/primitives/miriant/miriant.css +75 -0
- package/primitives/miriant/miriant.js +158 -0
- package/primitives/thann/thann.css +57 -0
- package/primitives/thann/thann.js +96 -0
- package/primitives/tiniath/tiniath.css +16 -0
- package/primitives/tiniath/tiniath.js +88 -0
- package/primitives/vircantie/vircantie.css +24 -0
- package/primitives/vircantie/vircantie.js +83 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cover Layout Custom Element
|
|
3
|
+
*
|
|
4
|
+
* Vertically centers a principal element with optional header/footer.
|
|
5
|
+
*
|
|
6
|
+
* @property {string} centered - Selector for the centered element (default: h1)
|
|
7
|
+
* @property {string} space - Minimum space around elements (default: var(--s1))
|
|
8
|
+
* @property {string} minHeight - Minimum height of cover (default: 100vh)
|
|
9
|
+
* @property {boolean} noPad - Remove padding from container
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <i-esgal centered="h2" min-height="80vh">
|
|
13
|
+
* <header>Logo</header>
|
|
14
|
+
* <h2>Main Title</h2>
|
|
15
|
+
* <footer>Scroll down</footer>
|
|
16
|
+
* </i-esgal>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
class EsgalLayout extends HTMLElement {
|
|
20
|
+
static get observedAttributes() {
|
|
21
|
+
return ['centered', 'space', 'min-height', 'no-pad'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.render = this.render.bind(this);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
connectedCallback() {
|
|
30
|
+
this.render();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
attributeChangedCallback() {
|
|
34
|
+
this.render();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get centered() {
|
|
38
|
+
return this.getAttribute('centered') || 'h1';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set centered(val) {
|
|
42
|
+
this.setAttribute('centered', val);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get space() {
|
|
46
|
+
return this.getAttribute('space') || 'var(--s1)';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set space(val) {
|
|
50
|
+
this.setAttribute('space', val);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get minHeight() {
|
|
54
|
+
return this.getAttribute('min-height') || '100vh';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set minHeight(val) {
|
|
58
|
+
this.setAttribute('min-height', val);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get noPad() {
|
|
62
|
+
return this.hasAttribute('no-pad');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
set noPad(val) {
|
|
66
|
+
if (val) {
|
|
67
|
+
this.setAttribute('no-pad', '');
|
|
68
|
+
} else {
|
|
69
|
+
this.removeAttribute('no-pad');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
render() {
|
|
74
|
+
this.style.setProperty('--esgal-min-height', this.minHeight);
|
|
75
|
+
this.style.setProperty('--esgal-space', this.space);
|
|
76
|
+
|
|
77
|
+
const noPadStr = this.noPad ? '-noPad' : '';
|
|
78
|
+
const id = `Esgal-${this.centered}-${this.minHeight}-${this.space}${noPadStr}`.replace(/[^\w-]/g, '');
|
|
79
|
+
this.dataset.i = id;
|
|
80
|
+
|
|
81
|
+
if (!document.getElementById(id)) {
|
|
82
|
+
const styleEl = document.createElement('style');
|
|
83
|
+
styleEl.id = id;
|
|
84
|
+
|
|
85
|
+
const selector = `[data-i="${id}"]`;
|
|
86
|
+
const padding = this.noPad ? '0' : this.space;
|
|
87
|
+
|
|
88
|
+
styleEl.textContent = `
|
|
89
|
+
${selector} {
|
|
90
|
+
min-block-size: ${this.minHeight};
|
|
91
|
+
padding: ${padding};
|
|
92
|
+
}
|
|
93
|
+
${selector} > * {
|
|
94
|
+
margin-block: ${this.space};
|
|
95
|
+
}
|
|
96
|
+
${selector} > :first-child:not(${this.centered}) {
|
|
97
|
+
margin-block-start: 0;
|
|
98
|
+
}
|
|
99
|
+
${selector} > :last-child:not(${this.centered}) {
|
|
100
|
+
margin-block-end: 0;
|
|
101
|
+
}
|
|
102
|
+
${selector} > ${this.centered} {
|
|
103
|
+
margin-block: auto;
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
document.head.appendChild(styleEl);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if ('customElements' in window) {
|
|
112
|
+
customElements.define('i-esgal', EsgalLayout);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default EsgalLayout;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imposter Layout
|
|
3
|
+
*
|
|
4
|
+
* Positions an element over content, centered within a
|
|
5
|
+
* positioning container. Perfect for modals and overlays.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
i-fano {
|
|
9
|
+
/* Default: absolute positioning relative to nearest positioned ancestor */
|
|
10
|
+
position: var(--fano-position, absolute);
|
|
11
|
+
|
|
12
|
+
/* Center the element */
|
|
13
|
+
inset-block-start: 50%;
|
|
14
|
+
inset-inline-start: 50%;
|
|
15
|
+
transform: translate(-50%, -50%);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Fixed positioning variant (relative to viewport) */
|
|
19
|
+
i-fano[fixed] {
|
|
20
|
+
--fano-position: fixed;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Contain within positioning container */
|
|
24
|
+
i-fano[contain] {
|
|
25
|
+
overflow: auto;
|
|
26
|
+
max-inline-size: calc(100% - (var(--fano-margin, 0px) * 2));
|
|
27
|
+
max-block-size: calc(100% - (var(--fano-margin, 0px) * 2));
|
|
28
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imposter Layout Custom Element
|
|
3
|
+
*
|
|
4
|
+
* Positions an element centered over a positioning container.
|
|
5
|
+
*
|
|
6
|
+
* @property {boolean} fixed - Use fixed positioning (viewport-relative)
|
|
7
|
+
* @property {boolean} contain - Prevent overflow outside container
|
|
8
|
+
* @property {string} margin - Minimum gap from container edges (when contained)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <div style="position: relative;">
|
|
12
|
+
* <p>Background content</p>
|
|
13
|
+
* <i-fano contain margin="var(--s1)">
|
|
14
|
+
* <dialog open>Modal content</dialog>
|
|
15
|
+
* </i-fano>
|
|
16
|
+
* </div>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
class FanoLayout extends HTMLElement {
|
|
20
|
+
static get observedAttributes() {
|
|
21
|
+
return ['fixed', 'contain', 'margin'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.render = this.render.bind(this);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
connectedCallback() {
|
|
30
|
+
this.render();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
attributeChangedCallback() {
|
|
34
|
+
this.render();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get fixed() {
|
|
38
|
+
return this.hasAttribute('fixed');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set fixed(val) {
|
|
42
|
+
if (val) {
|
|
43
|
+
this.setAttribute('fixed', '');
|
|
44
|
+
} else {
|
|
45
|
+
this.removeAttribute('fixed');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get contain() {
|
|
50
|
+
return this.hasAttribute('contain');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
set contain(val) {
|
|
54
|
+
if (val) {
|
|
55
|
+
this.setAttribute('contain', '');
|
|
56
|
+
} else {
|
|
57
|
+
this.removeAttribute('contain');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get margin() {
|
|
62
|
+
return this.getAttribute('margin') || '0px';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
set margin(val) {
|
|
66
|
+
this.setAttribute('margin', val);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
render() {
|
|
70
|
+
this.style.setProperty('--fano-margin', this.margin);
|
|
71
|
+
|
|
72
|
+
const fixedStr = this.fixed ? '-fixed' : '';
|
|
73
|
+
const containStr = this.contain ? '-contain' : '';
|
|
74
|
+
const id = `Fano-${this.margin}${fixedStr}${containStr}`.replace(/[^\w-]/g, '');
|
|
75
|
+
this.dataset.i = id;
|
|
76
|
+
|
|
77
|
+
if (!document.getElementById(id)) {
|
|
78
|
+
const styleEl = document.createElement('style');
|
|
79
|
+
styleEl.id = id;
|
|
80
|
+
|
|
81
|
+
const selector = `[data-i="${id}"]`;
|
|
82
|
+
let css = '';
|
|
83
|
+
|
|
84
|
+
if (this.fixed) {
|
|
85
|
+
css += `${selector} { position: fixed; }`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.contain) {
|
|
89
|
+
css += `
|
|
90
|
+
${selector} {
|
|
91
|
+
overflow: auto;
|
|
92
|
+
max-inline-size: calc(100% - (${this.margin} * 2));
|
|
93
|
+
max-block-size: calc(100% - (${this.margin} * 2));
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
styleEl.textContent = css;
|
|
99
|
+
document.head.appendChild(styleEl);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if ('customElements' in window) {
|
|
105
|
+
customElements.define('i-fano', FanoLayout);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default FanoLayout;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame Layout
|
|
3
|
+
*
|
|
4
|
+
* Constrains content to a specific aspect ratio.
|
|
5
|
+
* Perfect for images, videos, and embedded content.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
i-gant-thala {
|
|
9
|
+
display: block;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
|
|
12
|
+
/* Default 16:9 ratio */
|
|
13
|
+
gant-thala-ratio: var(--gant-thala-n, 16) / var(--gant-thala-d, 9);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* For images and videos, cover the frame */
|
|
17
|
+
i-gant-thala > img,
|
|
18
|
+
i-gant-thala > video {
|
|
19
|
+
inline-size: 100%;
|
|
20
|
+
block-size: 100%;
|
|
21
|
+
object-fit: cover;
|
|
22
|
+
/* Default: center the crop */
|
|
23
|
+
object-position: center;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Non-media content gets centered */
|
|
27
|
+
i-gant-thala > :not(img):not(video) {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
block-size: 100%;
|
|
32
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame Layout Custom Element
|
|
3
|
+
*
|
|
4
|
+
* Constrains content to a specific aspect ratio.
|
|
5
|
+
*
|
|
6
|
+
* @property {string} ratio - GantThala ratio as "n:d" (default: 16:9)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <i-gant-thala ratio="4:3">
|
|
10
|
+
* <img src="photo.jpg" alt="A photo">
|
|
11
|
+
* </i-gant-thala>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class GantThalaLayout extends HTMLElement {
|
|
15
|
+
static get observedAttributes() {
|
|
16
|
+
return ['ratio'];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
this.render = this.render.bind(this);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
connectedCallback() {
|
|
25
|
+
this.render();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
attributeChangedCallback() {
|
|
29
|
+
this.render();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get ratio() {
|
|
33
|
+
return this.getAttribute('ratio') || '16:9';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set ratio(val) {
|
|
37
|
+
this.setAttribute('ratio', val);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
render() {
|
|
41
|
+
const [n, d] = this.ratio.split(':').map(v => v.trim());
|
|
42
|
+
|
|
43
|
+
this.style.setProperty('--gant-thala-n', n);
|
|
44
|
+
this.style.setProperty('--gant-thala-d', d);
|
|
45
|
+
|
|
46
|
+
const id = `GantThala-${n}-${d}`.replace(/[^\w-]/g, '');
|
|
47
|
+
this.dataset.i = id;
|
|
48
|
+
|
|
49
|
+
if (!document.getElementById(id)) {
|
|
50
|
+
const styleEl = document.createElement('style');
|
|
51
|
+
styleEl.id = id;
|
|
52
|
+
|
|
53
|
+
const selector = `[data-i="${id}"]`;
|
|
54
|
+
|
|
55
|
+
styleEl.textContent = `
|
|
56
|
+
${selector} {
|
|
57
|
+
gant-thala-ratio: ${n} / ${d};
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
document.head.appendChild(styleEl);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if ('customElements' in window) {
|
|
66
|
+
customElements.define('i-gant-thala', GantThalaLayout);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default GantThalaLayout;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reel Layout
|
|
3
|
+
*
|
|
4
|
+
* Horizontal scrolling container for overflow content.
|
|
5
|
+
* A robust alternative to JavaScript carousels.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
i-glan-tholl {
|
|
9
|
+
display: flex;
|
|
10
|
+
block-size: var(--glan-tholl-height, auto);
|
|
11
|
+
|
|
12
|
+
overflow-x: auto;
|
|
13
|
+
overflow-y: hidden;
|
|
14
|
+
|
|
15
|
+
/* Smooth scrolling where supported and preferred */
|
|
16
|
+
scroll-behavior: smooth;
|
|
17
|
+
|
|
18
|
+
/* Scroll snap for better UX */
|
|
19
|
+
scroll-snap-type: x mandatory;
|
|
20
|
+
|
|
21
|
+
/* Firefox scrollbar styling */
|
|
22
|
+
scrollbar-color: var(--color-dark) var(--color-light);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
i-glan-tholl > * {
|
|
26
|
+
flex: 0 0 var(--glan-tholl-item-width, auto);
|
|
27
|
+
scroll-snap-align: start;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
i-glan-tholl > * + * {
|
|
31
|
+
margin-inline-start: var(--glan-tholl-space, var(--s1));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Images should maintain aspect ratio */
|
|
35
|
+
i-glan-tholl > img {
|
|
36
|
+
block-size: 100%;
|
|
37
|
+
inline-size: auto;
|
|
38
|
+
flex-basis: auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Webkit scrollbar styling */
|
|
42
|
+
i-glan-tholl::-webkit-scrollbar {
|
|
43
|
+
block-size: 0.75rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
i-glan-tholl::-webkit-scrollbar-track {
|
|
47
|
+
background-color: var(--color-light);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
i-glan-tholl::-webkit-scrollbar-thumb {
|
|
51
|
+
background-color: var(--color-mid);
|
|
52
|
+
border-radius: var(--s-2);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Hide scrollbar variant */
|
|
56
|
+
i-glan-tholl[no-bar] {
|
|
57
|
+
scrollbar-width: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
i-glan-tholl[no-bar]::-webkit-scrollbar {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Overflowing state (added via JS) */
|
|
65
|
+
i-glan-tholl.overflowing {
|
|
66
|
+
padding-block-end: var(--glan-tholl-space, var(--s1));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
i-glan-tholl.overflowing.no-bar {
|
|
70
|
+
padding-block-end: 0;
|
|
71
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reel Layout Custom Element
|
|
3
|
+
*
|
|
4
|
+
* Horizontal scrolling container with native browser scrolling.
|
|
5
|
+
* Uses ResizeObserver and MutationObserver for overflow detection.
|
|
6
|
+
*
|
|
7
|
+
* @property {string} itemWidth - Width of each item (default: auto)
|
|
8
|
+
* @property {string} space - Gap between items (default: var(--s1))
|
|
9
|
+
* @property {string} height - Height of the reel (default: auto)
|
|
10
|
+
* @property {boolean} noBar - Hide the scrollbar
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <i-glan-tholl item-width="300px" space="var(--s2)">
|
|
14
|
+
* <div>Card 1</div>
|
|
15
|
+
* <div>Card 2</div>
|
|
16
|
+
* <div>Card 3</div>
|
|
17
|
+
* </i-glan-tholl>
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
class GlanThollLayout extends HTMLElement {
|
|
21
|
+
static get observedAttributes() {
|
|
22
|
+
return ['item-width', 'space', 'height', 'no-bar'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
this.render = this.render.bind(this);
|
|
28
|
+
this.toggleOverflowClass = this.toggleOverflowClass.bind(this);
|
|
29
|
+
this.resizeObserver = null;
|
|
30
|
+
this.mutationObserver = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
connectedCallback() {
|
|
34
|
+
this.render();
|
|
35
|
+
this.setupObservers();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
disconnectedCallback() {
|
|
39
|
+
if (this.resizeObserver) {
|
|
40
|
+
this.resizeObserver.disconnect();
|
|
41
|
+
}
|
|
42
|
+
if (this.mutationObserver) {
|
|
43
|
+
this.mutationObserver.disconnect();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
attributeChangedCallback() {
|
|
48
|
+
this.render();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get itemWidth() {
|
|
52
|
+
return this.getAttribute('item-width') || 'auto';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
set itemWidth(val) {
|
|
56
|
+
this.setAttribute('item-width', val);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get space() {
|
|
60
|
+
return this.getAttribute('space') || 'var(--s1)';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
set space(val) {
|
|
64
|
+
this.setAttribute('space', val);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get height() {
|
|
68
|
+
return this.getAttribute('height') || 'auto';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
set height(val) {
|
|
72
|
+
this.setAttribute('height', val);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get noBar() {
|
|
76
|
+
return this.hasAttribute('no-bar');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
set noBar(val) {
|
|
80
|
+
if (val) {
|
|
81
|
+
this.setAttribute('no-bar', '');
|
|
82
|
+
} else {
|
|
83
|
+
this.removeAttribute('no-bar');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
toggleOverflowClass() {
|
|
88
|
+
this.classList.toggle('overflowing', this.scrollWidth > this.clientWidth);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setupObservers() {
|
|
92
|
+
// ResizeObserver for container size changes
|
|
93
|
+
if ('ResizeObserver' in window) {
|
|
94
|
+
this.resizeObserver = new ResizeObserver(entries => {
|
|
95
|
+
this.toggleOverflowClass();
|
|
96
|
+
});
|
|
97
|
+
this.resizeObserver.observe(this);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MutationObserver for child changes
|
|
101
|
+
if ('MutationObserver' in window) {
|
|
102
|
+
this.mutationObserver = new MutationObserver(entries => {
|
|
103
|
+
this.toggleOverflowClass();
|
|
104
|
+
});
|
|
105
|
+
this.mutationObserver.observe(this, { childList: true });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Initial check
|
|
109
|
+
this.toggleOverflowClass();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
render() {
|
|
113
|
+
this.style.setProperty('--glan-tholl-item-width', this.itemWidth);
|
|
114
|
+
this.style.setProperty('--glan-tholl-space', this.space);
|
|
115
|
+
this.style.setProperty('--glan-tholl-height', this.height);
|
|
116
|
+
|
|
117
|
+
const noBarStr = this.noBar ? '-noBar' : '';
|
|
118
|
+
const id = `GlanTholl-${this.itemWidth}-${this.space}-${this.height}${noBarStr}`.replace(/[^\w-]/g, '');
|
|
119
|
+
this.dataset.i = id;
|
|
120
|
+
|
|
121
|
+
if (!document.getElementById(id)) {
|
|
122
|
+
const styleEl = document.createElement('style');
|
|
123
|
+
styleEl.id = id;
|
|
124
|
+
|
|
125
|
+
const selector = `[data-i="${id}"]`;
|
|
126
|
+
|
|
127
|
+
styleEl.textContent = `
|
|
128
|
+
${selector} {
|
|
129
|
+
block-size: ${this.height};
|
|
130
|
+
}
|
|
131
|
+
${selector} > * {
|
|
132
|
+
flex: 0 0 ${this.itemWidth};
|
|
133
|
+
}
|
|
134
|
+
${selector} > * + * {
|
|
135
|
+
margin-inline-start: ${this.space};
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
document.head.appendChild(styleEl);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if ('customElements' in window) {
|
|
144
|
+
customElements.define('i-glan-tholl', GlanThollLayout);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default GlanThollLayout;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GlanVeleg Layout
|
|
3
|
+
*
|
|
4
|
+
* A two-element layout where one has a fixed width (sidebar)
|
|
5
|
+
* and the other fills remaining space. Switches to vertical
|
|
6
|
+
* when the non-sidebar would be less than a threshold.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
i-glan-veleg {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-wrap: wrap;
|
|
12
|
+
gap: var(--glan-veleg-space, var(--s1));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Default: sidebar is the first child (left side) */
|
|
16
|
+
i-glan-veleg > * {
|
|
17
|
+
flex-grow: 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
i-glan-veleg > :first-child {
|
|
21
|
+
flex-basis: var(--glan-veleg-width, 20rem);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
i-glan-veleg > :last-child {
|
|
25
|
+
flex-basis: 0;
|
|
26
|
+
flex-grow: 999;
|
|
27
|
+
min-inline-size: var(--glan-veleg-content-min, 50%);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* When sidebar is on the right */
|
|
31
|
+
i-glan-veleg[side="right"] > :first-child {
|
|
32
|
+
flex-basis: 0;
|
|
33
|
+
flex-grow: 999;
|
|
34
|
+
min-inline-size: var(--glan-veleg-content-min, 50%);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
i-glan-veleg[side="right"] > :last-child {
|
|
38
|
+
flex-basis: var(--glan-veleg-width, 20rem);
|
|
39
|
+
flex-grow: 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Disable equal height stretching */
|
|
43
|
+
i-glan-veleg[no-stretch] {
|
|
44
|
+
align-items: flex-start;
|
|
45
|
+
}
|