beercss 3.6.13 → 3.7.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.
@@ -0,0 +1,142 @@
1
+ import { query, hasClass, on, next, prev, hasType, parent, queryAll, run } from "../utils";
2
+
3
+ function onClickLabel(e: Event) {
4
+ const label = e.currentTarget as HTMLLabelElement;
5
+ const field = parent(label);
6
+ const input = query("input:not([type=file], [type=checkbox], [type=radio]), select, textarea", field) as HTMLElement;
7
+ if (input) input.focus();
8
+ }
9
+
10
+ function onFocusInput(e: Event) {
11
+ const input = e.currentTarget as HTMLInputElement;
12
+ updateInput(input);
13
+ }
14
+
15
+ function onBlurInput(e: Event) {
16
+ const input = e.currentTarget as HTMLInputElement;
17
+ updateInput(input);
18
+ }
19
+
20
+ function onChangeFile(e: Event) {
21
+ const input = e.currentTarget as HTMLInputElement;
22
+ updateFile(input);
23
+ }
24
+
25
+ function onChangeColor(e: Event) {
26
+ const input = e.currentTarget as HTMLInputElement;
27
+ updateColor(input);
28
+ }
29
+
30
+ function onKeydownFile(e: KeyboardEvent) {
31
+ const input = e.currentTarget as HTMLInputElement;
32
+ updateFile(input, e);
33
+ }
34
+
35
+ function onKeydownColor(e: KeyboardEvent) {
36
+ const input = e.currentTarget as HTMLInputElement;
37
+ updateColor(input, e);
38
+ }
39
+
40
+ function onInputTextarea(e: Event) {
41
+ const textarea = e.currentTarget as HTMLTextAreaElement;
42
+ updateTextarea(textarea);
43
+ }
44
+
45
+ function updateAllLabels() {
46
+ const labels = queryAll(".field > label");
47
+ for (let i=0; i<labels.length; i++) on(labels[i], "click", onClickLabel);
48
+ }
49
+
50
+ function updateAllInputs() {
51
+ const inputs = queryAll(".field > input:not([type=file], [type=color], [type=range])") as NodeListOf<HTMLInputElement>;
52
+ for (let i=0; i<inputs.length; i++) {
53
+ on(inputs[i], "focus", onFocusInput);
54
+ on(inputs[i], "blur", onBlurInput);
55
+ updateInput(inputs[i]);
56
+ }
57
+ }
58
+
59
+ function updateAllSelects() {
60
+ const selects = queryAll(".field > select") as NodeListOf<HTMLSelectElement>;
61
+ for (let i=0; i<selects.length; i++) {
62
+ on(selects[i], "focus", onFocusInput);
63
+ on(selects[i], "blur", onBlurInput);
64
+ }
65
+ }
66
+
67
+ function updateAllFiles() {
68
+ const files = queryAll(".field > input[type=file]") as NodeListOf<HTMLInputElement>;
69
+ for (let i=0; i<files.length; i++) {
70
+ on(files[i], "change", onChangeFile);
71
+ updateFile(files[i]);
72
+ }
73
+ }
74
+
75
+ function updateAllColors() {
76
+ const colors = queryAll(".field > input[type=color]") as NodeListOf<HTMLInputElement>;
77
+ for (let i=0; i<colors.length; i++) {
78
+ on(colors[i], "change", onChangeColor);
79
+ updateColor(colors[i]);
80
+ }
81
+ }
82
+
83
+ function updateAllTextareas() {
84
+ const textareas = queryAll(".field.textarea > textarea") as NodeListOf<HTMLTextAreaElement>;
85
+ for (let i=0; i<textareas.length; i++) {
86
+ on(textareas[i], "focus", onFocusInput);
87
+ on(textareas[i], "blur", onBlurInput);
88
+ on(textareas[i], "input", onInputTextarea);
89
+ updateTextarea(textareas[i]);
90
+ }
91
+ }
92
+
93
+ function updateInput(input: HTMLInputElement) {
94
+ if (hasType(input, "number") && !input.value) input.value = "";
95
+ if (!input.placeholder) input.placeholder = " ";
96
+ if (input.getAttribute("data-ui")) run(input, null);
97
+ }
98
+
99
+ function updateFile(input: HTMLInputElement, e?: KeyboardEvent) {
100
+ if (e?.key === "Enter") {
101
+ const previousInput = prev(input) as HTMLInputElement;
102
+ if (!hasType(previousInput, "file")) return;
103
+ previousInput.click(); return;
104
+ }
105
+
106
+ const nextInput = next(input) as HTMLInputElement;
107
+ if (!hasType(nextInput, "text")) return;
108
+ nextInput.value = input.files ? Array.from(input.files).map((x) => x.name).join(", ") : "";
109
+ nextInput.readOnly = true;
110
+ on(nextInput, "keydown", onKeydownFile, false);
111
+ updateInput(nextInput);
112
+ }
113
+
114
+ function updateColor(input: HTMLInputElement, e?: KeyboardEvent) {
115
+ if (e?.key === "Enter") {
116
+ const previousInput = prev(input) as HTMLInputElement;
117
+ if (!hasType(previousInput, "color")) return;
118
+ previousInput.click(); return;
119
+ }
120
+
121
+ const nextInput = next(input) as HTMLInputElement;
122
+ if (!hasType(nextInput, "text")) return;
123
+ nextInput.readOnly = true;
124
+ nextInput.value = input.value;
125
+ on(nextInput, "keydown", onKeydownColor, false);
126
+ updateInput(nextInput);
127
+ }
128
+
129
+ function updateTextarea(textarea: HTMLTextAreaElement) {
130
+ const field = parent(textarea) as HTMLElement;
131
+ field.removeAttribute("style");
132
+ if (hasClass(field, "min")) field.style.setProperty("---size", `${Math.max(textarea.scrollHeight, field.offsetHeight)}px`);
133
+ }
134
+
135
+ export function updateAllFields() {
136
+ updateAllLabels();
137
+ updateAllInputs();
138
+ updateAllSelects();
139
+ updateAllFiles();
140
+ updateAllColors();
141
+ updateAllTextareas();
142
+ }
@@ -1,4 +1,6 @@
1
- i {
1
+ i,
2
+ :is(.checkbox, .radio, .switch) > span::before,
3
+ .icon > span > i {
2
4
  ---size: 1.5rem;
3
5
 
4
6
  font-family: var(--font-icon);
@@ -37,7 +37,7 @@ menu.no-wrap {
37
37
 
38
38
  menu.active,
39
39
  menu:not([data-ui]):active,
40
- :not([data-ui]):focus-within > menu,
40
+ :not(menu, [data-ui]):focus-within > menu,
41
41
  menu > :is(a, li):hover + menu,
42
42
  menu > menu:hover {
43
43
  opacity: 1;
@@ -0,0 +1,40 @@
1
+ import { query, queryAll, addClass, on, off, hasTag, hasClass, removeClass, blurActiveElement } from "../utils";
2
+
3
+ let _timeoutMenu: ReturnType<typeof setTimeout>;
4
+
5
+ function onClickDocument(e: Event) {
6
+ off(document.body, "click", onClickDocument);
7
+ const body = e.target as Element;
8
+ const menus = queryAll("menu.active") as NodeListOf<HTMLMenuElement>;
9
+ for (let i=0; i<menus.length; i++) updateMenu(body, menus[i], e);
10
+ }
11
+
12
+ function focusOnMenuOrInput(menu: HTMLMenuElement) {
13
+ setTimeout(() => {
14
+ const input = query(".field > input", menu) as HTMLInputElement;
15
+ if (input) input.focus();
16
+ else menu.focus();
17
+ }, 90);
18
+ }
19
+
20
+ export function updateMenu(from: Element, menu: HTMLMenuElement, e?: Event) {
21
+ if (_timeoutMenu) clearTimeout(_timeoutMenu);
22
+
23
+ _timeoutMenu = setTimeout(() => {
24
+ on(document.body, "click", onClickDocument);
25
+ if (!hasTag(document.activeElement, "input")) blurActiveElement();
26
+
27
+ const isActive = hasClass(menu, "active");
28
+ const isEvent = !!(e?.target === from);
29
+ const isChild = !!from.closest("menu");
30
+
31
+ if ((!isActive && isChild) || (isActive && isEvent)) {
32
+ removeClass(menu, "active");
33
+ return;
34
+ }
35
+
36
+ removeClass(queryAll("menu.active"), "active");
37
+ addClass(menu, "active");
38
+ focusOnMenuOrInput(menu);
39
+ }, 90);
40
+ }
@@ -179,6 +179,8 @@ nav:is(.left, .right, .top, .bottom):not(.drawer) > :is(ol, ul) > li > a:not(.bu
179
179
  align-self: center;
180
180
  display: flex;
181
181
  flex-direction: column;
182
+ gap: 0.25rem;
183
+ line-height: normal;
182
184
  }
183
185
 
184
186
  nav:is(.top, .bottom):not(.drawer) > a:not(.button, .chip),
@@ -0,0 +1,7 @@
1
+ import { addClass, parent, removeClass, queryAll } from "../utils";
2
+
3
+ export function updatePage(page: Element) {
4
+ const container = parent(page);
5
+ if (container) removeClass(queryAll(".page", container), "active");
6
+ addClass(page, "active");
7
+ }
@@ -36,23 +36,9 @@
36
36
  }
37
37
 
38
38
  :is(.checkbox, .radio, .switch) > span::before,
39
- .icon > span > i {
40
- font-family: var(--font-icon);
41
- font-weight: normal;
42
- font-style: normal;
43
- font-size: 1.5rem;
44
- line-height: 1;
45
- letter-spacing: normal;
46
- text-transform: none;
47
- display: inline-block;
48
- white-space: nowrap;
49
- word-wrap: normal;
50
- direction: ltr;
51
- font-feature-settings: "liga";
52
- -webkit-font-smoothing: antialiased;
53
- vertical-align: middle;
54
- text-align: center;
55
- overflow: hidden;
39
+ .icon > span > i,
40
+ :is(.checkbox, .radio) > span::after {
41
+ content: '';
56
42
  inline-size: 1.5rem;
57
43
  block-size: 1.5rem;
58
44
  box-sizing: border-box;
@@ -61,12 +47,9 @@
61
47
  color: var(--primary);
62
48
  position: absolute;
63
49
  inset: auto auto auto -1.5rem;
64
- background-color: transparent;
65
50
  border-radius: 50%;
66
51
  user-select: none;
67
52
  z-index: 1;
68
- box-shadow: 0 0 0 0 var(--active);
69
- transition: all var(--speed1);
70
53
  }
71
54
 
72
55
  .switch > span::before,
@@ -121,9 +104,17 @@
121
104
  font-variation-settings: unset !important;
122
105
  }
123
106
 
124
- :is(.checkbox, .radio) > input:not(:disabled):is(:focus, :hover) + span::before {
125
- background-color: var(--active);
126
- box-shadow: 0 0 0 0.5rem var(--active);
107
+ :is(.checkbox, .radio) > span::after {
108
+ transition: all var(--speed1);
109
+ background-color: currentColor;
110
+ box-shadow: 0 0 0 0 currentColor;
111
+ opacity: 0;
112
+ }
113
+
114
+ :is(.checkbox, .radio):is(:hover) > input:not(:disabled) + span::after,
115
+ :is(.checkbox, .radio) > input:not(:disabled):is(:focus) + span::after {
116
+ box-shadow: 0 0 0 0.5rem currentColor;
117
+ opacity: 0.1;
127
118
  }
128
119
 
129
120
  .switch > input:not(:disabled):is(:focus, :hover) + span::before,
@@ -49,6 +49,7 @@
49
49
  z-index: 1;
50
50
  padding: 0;
51
51
  margin: 0;
52
+ transform: rotate(0deg);
52
53
  }
53
54
 
54
55
  .slider > input:only-of-type {
@@ -252,7 +253,7 @@
252
253
  inset: var(---end) 0 var(---start) 0;
253
254
  }
254
255
 
255
- @media (hover: none){
256
+ @media (pointer: coarse) {
256
257
  .slider > :hover ~ .tooltip {
257
258
  inset-block-start: -1rem !important;
258
259
  opacity: 1 !important;
@@ -0,0 +1,86 @@
1
+ import { query, queryAll, hasClass, on, off, parent, hasTag, isTouchable } from "../utils";
2
+
3
+ function onInputDocument(e: Event) {
4
+ const input = e.target as HTMLInputElement;
5
+ if (!hasTag(input, "input") && !hasTag(input, "select")) return;
6
+
7
+ if (input.type === "range") {
8
+ input.focus();
9
+ updateRange(input);
10
+ } else {
11
+ updateAllRanges();
12
+ }
13
+ }
14
+
15
+ function onFocusRange(e: Event) {
16
+ if (!isTouchable()) return;
17
+
18
+ const input = e.target as HTMLInputElement;
19
+ const label = parent(input) as HTMLLabelElement;
20
+ if (hasClass(label, "vertical")) document.body.classList.add("no-scroll");
21
+ }
22
+
23
+ function onBlurRange(e: Event) {
24
+ if (!isTouchable()) return;
25
+
26
+ const input = e.target as HTMLInputElement;
27
+ const label = parent(input) as HTMLLabelElement;
28
+ if (hasClass(label, "vertical")) document.body.classList.remove("no-scroll");
29
+ }
30
+
31
+ function updateAllRanges() {
32
+ const body = document.body;
33
+ const ranges = queryAll(".slider > input[type=range]") as NodeListOf<HTMLInputElement>;
34
+ if (!ranges.length) off(body, "input", onInputDocument, false);
35
+ else on(body, "input", onInputDocument, false);
36
+ for(let i=0; i<ranges.length; i++) updateRange(ranges[i]);
37
+ }
38
+
39
+ function updateRange(input: HTMLInputElement) {
40
+ on(input, "focus", onFocusRange);
41
+ on(input, "blur", onBlurRange);
42
+
43
+ const label = parent(input) as HTMLElement;
44
+ const bar = query("span", label) as HTMLElement;
45
+ const inputs = queryAll("input", label) as NodeListOf<HTMLInputElement>;
46
+ if (!inputs.length || !bar) return;
47
+
48
+ const rootSize = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--size")) || 16;
49
+ const thumb = hasClass(label, "max") ? 0 : 0.25 * rootSize * 100 / inputs[0].offsetWidth;
50
+ const percents: Array<number> = [];
51
+ const values: Array<number> = [];
52
+ for (let i = 0, n = inputs.length; i < n; i++) {
53
+ const min = parseFloat(inputs[i].min) || 0;
54
+ const max = parseFloat(inputs[i].max) || 100;
55
+ const value = parseFloat(inputs[i].value) || 0;
56
+ const percent = (value - min) * 100 / (max - min);
57
+ const fix = thumb / 2 - thumb * percent / 100;
58
+ percents.push(percent + fix);
59
+ values.push(value);
60
+ }
61
+
62
+ let percent = percents[0];
63
+ let start = 0;
64
+ let end = 100 - start - percent;
65
+ let value1 = values[0];
66
+ let value2 = values[1] || 0;
67
+ if (inputs.length > 1) {
68
+ percent = Math.abs(percents[1] - percents[0]);
69
+ start = percents[1] > percents[0] ? percents[0] : percents[1];
70
+ end = 100 - start - percent;
71
+
72
+ if (value2 > value1) {
73
+ value1 = values[1] || 0;
74
+ value2 = values[0];
75
+ }
76
+ }
77
+
78
+ label.style.setProperty("---start", `${start}%`);
79
+ label.style.setProperty("---end", `${end}%`);
80
+ label.style.setProperty("---value1", `'${value1}'`);
81
+ label.style.setProperty("---value2", `'${value2}'`);
82
+ }
83
+
84
+ export function updateAllSliders() {
85
+ updateAllRanges();
86
+ }
@@ -0,0 +1,27 @@
1
+ import { queryAll, addClass, on, removeClass, blurActiveElement } from "../utils";
2
+
3
+ let _timeoutSnackbar: ReturnType<typeof setTimeout>;
4
+
5
+ function onClickSnackbar(e: Event) {
6
+ const snackbar = e.currentTarget as Element;
7
+ removeClass(snackbar, "active");
8
+
9
+ if (_timeoutSnackbar) clearTimeout(_timeoutSnackbar);
10
+ }
11
+
12
+ export function updateSnackbar(snackbar: Element, milliseconds?: number) {
13
+ blurActiveElement();
14
+
15
+ const activeSnackbars = queryAll(".snackbar.active");
16
+ for(let i=0; i<activeSnackbars.length; i++) removeClass(activeSnackbars[i], "active");
17
+ addClass(snackbar, "active");
18
+ on(snackbar, "click", onClickSnackbar);
19
+
20
+ if (_timeoutSnackbar) clearTimeout(_timeoutSnackbar);
21
+
22
+ if (milliseconds === -1) return;
23
+
24
+ _timeoutSnackbar = setTimeout(() => {
25
+ removeClass(snackbar, "active");
26
+ }, milliseconds ?? 6000);
27
+ }
@@ -0,0 +1,84 @@
1
+ import { type IBeerCssTheme } from "../interfaces";
2
+ import { isDark } from "../utils";
3
+
4
+ const _lastTheme: IBeerCssTheme = {
5
+ light: "",
6
+ dark: "",
7
+ };
8
+
9
+ function getMode() {
10
+ return document?.body?.classList.contains("dark") ? "dark" : "light";
11
+ }
12
+
13
+ function lastTheme(): IBeerCssTheme {
14
+ if (_lastTheme.light && _lastTheme.dark) return _lastTheme;
15
+ const body = document.body;
16
+
17
+ const light = document.createElement("body");
18
+ light.className = "light";
19
+ body.appendChild(light);
20
+
21
+ const dark = document.createElement("body");
22
+ dark.className = "dark";
23
+ body.appendChild(dark);
24
+
25
+ const fromLight = getComputedStyle(light);
26
+ const fromDark = getComputedStyle(dark);
27
+ const variables = ["--primary", "--on-primary", "--primary-container", "--on-primary-container", "--secondary", "--on-secondary", "--secondary-container", "--on-secondary-container", "--tertiary", "--on-tertiary", "--tertiary-container", "--on-tertiary-container", "--error", "--on-error", "--error-container", "--on-error-container", "--background", "--on-background", "--surface", "--on-surface", "--surface-variant", "--on-surface-variant", "--outline", "--outline-variant", "--shadow", "--scrim", "--inverse-surface", "--inverse-on-surface", "--inverse-primary", "--surface-dim", "--surface-bright", "--surface-container-lowest", "--surface-container-low", "--surface-container", "--surface-container-high", "--surface-container-highest"];
28
+ for (let i = 0, n = variables.length; i < n; i++) {
29
+ _lastTheme.light += variables[i] + ":" + fromLight.getPropertyValue(variables[i]) + ";";
30
+ _lastTheme.dark += variables[i] + ":" + fromDark.getPropertyValue(variables[i]) + ";";
31
+ }
32
+
33
+ body.removeChild(light);
34
+ body.removeChild(dark);
35
+ return _lastTheme;
36
+ }
37
+
38
+ export function updateTheme(source?: IBeerCssTheme | any): IBeerCssTheme | Promise<IBeerCssTheme> {
39
+ const context = globalThis as any;
40
+ const body = document.body;
41
+ if (!source || !context.materialDynamicColors) return lastTheme();
42
+
43
+ const mode = getMode();
44
+ if (source.light && source.dark) {
45
+ _lastTheme.light = source.light;
46
+ _lastTheme.dark = source.dark;
47
+ body.setAttribute("style", source[mode]);
48
+ return source;
49
+ }
50
+
51
+ return context.materialDynamicColors(source).then((theme: IBeerCssTheme) => {
52
+ const toCss = (data: any) => {
53
+ let style = "";
54
+ for (let i = 0, keys = Object.keys(data), n = keys.length; i < n; i++) {
55
+ const key = keys[i];
56
+ const value = data[key] as string;
57
+ const kebabCase = key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
58
+ style += "--" + kebabCase + ":" + value + ";";
59
+ }
60
+ return style;
61
+ };
62
+
63
+ _lastTheme.light = toCss(theme.light);
64
+ _lastTheme.dark = toCss(theme.dark);
65
+ body.setAttribute("style", _lastTheme[mode]);
66
+ return _lastTheme;
67
+ });
68
+ }
69
+
70
+ export function updateMode(value: string): string {
71
+ const context = (globalThis as any);
72
+ const body = document.body;
73
+
74
+ if (!body) return value;
75
+ if (!value) return getMode();
76
+ if (value === "auto") value = isDark() ? "dark" : "light";
77
+
78
+ body.classList.remove("light", "dark");
79
+ body.classList.add(value);
80
+
81
+ const lastThemeStyle = value === "light" ? _lastTheme.light : _lastTheme.dark;
82
+ if (context.materialDynamicColors) body.setAttribute("style", lastThemeStyle);
83
+ return getMode();
84
+ }
@@ -10,14 +10,14 @@
10
10
  inline-size: 100%;
11
11
  block-size: 100%;
12
12
  background-position: center;
13
- background-image: radial-gradient(circle, var(--active) 1%, transparent 1%);
13
+ background-image: radial-gradient(circle, currentColor 1%, transparent 1%);
14
14
  opacity: 0;
15
15
  transition: none;
16
16
  }
17
17
 
18
18
  :is(.wave, .chip, .button, button, nav.tabbed > a, .tabs > a):is(:focus-visible, :hover)::after {
19
19
  background-size: 15000%;
20
- opacity: 1;
20
+ opacity: 0.1;
21
21
  transition: background-size var(--speed2) linear;
22
22
  }
23
23
 
@@ -6,7 +6,7 @@
6
6
  font-display: block;
7
7
  src:
8
8
  url("../material-symbols-outlined.woff2") format("woff2"),
9
- url("https://cdn.jsdelivr.net/npm/beercss@3.6.13/dist/cdn/material-symbols-outlined.woff2") format("woff2");
9
+ url("https://cdn.jsdelivr.net/npm/beercss@3.7.0/dist/cdn/material-symbols-outlined.woff2") format("woff2");
10
10
  }
11
11
 
12
12
  /* rounded icons */
@@ -17,7 +17,7 @@
17
17
  font-display: block;
18
18
  src:
19
19
  url("../material-symbols-rounded.woff2") format("woff2"),
20
- url("https://cdn.jsdelivr.net/npm/beercss@3.6.13/dist/cdn/material-symbols-rounded.woff2") format("woff2");
20
+ url("https://cdn.jsdelivr.net/npm/beercss@3.7.0/dist/cdn/material-symbols-rounded.woff2") format("woff2");
21
21
  }
22
22
 
23
23
  /* sharp icons */
@@ -28,5 +28,5 @@
28
28
  font-display: block;
29
29
  src:
30
30
  url("../material-symbols-sharp.woff2") format("woff2"),
31
- url("https://cdn.jsdelivr.net/npm/beercss@3.6.13/dist/cdn/material-symbols-sharp.woff2") format("woff2");
31
+ url("https://cdn.jsdelivr.net/npm/beercss@3.7.0/dist/cdn/material-symbols-sharp.woff2") format("woff2");
32
32
  }