@stanko/ctrls 0.4.3 → 0.4.5

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/README.md CHANGED
@@ -313,7 +313,7 @@ Grid of radio buttons. Can be set to have between one and five columns.
313
313
  ```ts
314
314
  {
315
315
  // Mandatory
316
- items: Record<string, string>; // Map of radio items (key, value)
316
+ items: Record<string, string>; // Map of radio items (key -> value, value -> label)
317
317
 
318
318
  // Optional
319
319
  columns?: number; // default: 3
@@ -481,11 +481,12 @@ Thank you for stopping by! If you end up using Ctrls, please let me know, I woul
481
481
  ## TODO
482
482
 
483
483
  * [ ] Add `add/removeEventListener` instead of direct update of `onInput` and `onChange`
484
+ * [ ] Check if `onInput` is triggered when it should
484
485
  * [ ] Allow users to pass a custom PRNG lib
485
486
  * [ ] Hash storage - check if there is an instance using hash storage already
486
487
  * [ ] Storage - local storage
487
- * [ ] Check if `onInput` si triggered when it should
488
- * [ ] Use dom helpers everywhere
488
+ * [*] Collapsing animation for the main element and groups
489
+ * [x] Use dom helpers everywhere
489
490
  * [x] `toHtmlId(this.name)` => `toHtmlId(this.id)`
490
491
  * [x] Add `name` and `id` to inputs
491
492
  * [x] TypeDef bug - easing presets should be optional
@@ -1,4 +1,4 @@
1
- import type { Ctrl, CtrlItemType, CtrlChangeHandler, CtrlConfig } from "./types";
1
+ import type { Ctrl, CtrlChangeHandler, CtrlConfig, CtrlItemType } from "./types";
2
2
  export declare class BooleanCtrl implements Ctrl<boolean> {
3
3
  type: CtrlItemType;
4
4
  id: string;
@@ -1,3 +1,4 @@
1
+ import { dom } from "../utils/dom";
1
2
  import { checkIcon } from "../utils/icons";
2
3
  import { toHtmlId } from "../utils/string-utils";
3
4
  export class BooleanCtrl {
@@ -17,11 +18,11 @@ export class BooleanCtrl {
17
18
  };
18
19
  this.buildUI = () => {
19
20
  const id = toHtmlId(this.id);
20
- const input = document.createElement("input");
21
- input.classList.add("ctrls__boolean-input");
22
- input.setAttribute("type", "checkbox");
23
- input.setAttribute("id", id);
24
- input.setAttribute("name", id);
21
+ const input = dom.input("ctrls__boolean-input", {
22
+ type: "checkbox",
23
+ id,
24
+ name: id,
25
+ });
25
26
  input.checked = this.value;
26
27
  input.addEventListener("change", () => {
27
28
  this.value = input.checked;
@@ -31,20 +32,18 @@ export class BooleanCtrl {
31
32
  this.value = input.checked;
32
33
  this.onInput(this);
33
34
  });
34
- const checkmark = document.createElement("span");
35
- checkmark.classList.add("ctrls__boolean-checkmark");
36
- checkmark.innerHTML = checkIcon;
37
- const right = document.createElement("div");
38
- right.classList.add("ctrls__control-right");
39
- right.appendChild(input);
40
- right.appendChild(checkmark);
41
- const label = document.createElement("span");
42
- label.textContent = this.label;
43
- label.classList.add("ctrls__control-label");
44
- const element = document.createElement("label");
45
- element.classList.add("ctrls__control", "ctrls__control--boolean");
46
- element.appendChild(label);
47
- element.appendChild(right);
35
+ const checkmark = dom.span("ctrls__boolean-checkmark", {
36
+ innerHTML: checkIcon,
37
+ });
38
+ const right = dom.div("ctrls__control-right", {
39
+ children: [input, checkmark],
40
+ });
41
+ const label = dom.span("ctrls__control-label", {
42
+ children: [this.label],
43
+ });
44
+ const element = dom.label("ctrls__control ctrls__control--boolean", {
45
+ children: [label, right],
46
+ });
48
47
  return {
49
48
  element,
50
49
  input,
@@ -1,5 +1,5 @@
1
1
  import DualRangeInput from "@stanko/dual-range-input";
2
- import type { Ctrl, CtrlChangeHandler, CtrlItemType, ConfigFor } from "./types";
2
+ import type { ConfigFor, Ctrl, CtrlChangeHandler, CtrlItemType } from "./types";
3
3
  export type DualRangeControlOptions = {
4
4
  min: number;
5
5
  max: number;
@@ -1,5 +1,6 @@
1
- import random from "../utils/random";
2
1
  import DualRangeInput from "@stanko/dual-range-input";
2
+ import { dom } from "../utils/dom";
3
+ import random from "../utils/random";
3
4
  import { roundToStep } from "../utils/round-to-step";
4
5
  import { toHtmlId } from "../utils/string-utils";
5
6
  export class DualRangeCtrl {
@@ -46,43 +47,41 @@ export class DualRangeCtrl {
46
47
  this.update(this.value);
47
48
  this.onInput(this);
48
49
  };
49
- const minInput = document.createElement("input");
50
- minInput.setAttribute("type", "range");
51
- minInput.setAttribute("name", `${id}-min`);
52
- minInput.setAttribute("id", `${id}-min`);
53
- minInput.setAttribute("min", min.toString());
54
- minInput.setAttribute("max", max.toString());
55
- minInput.setAttribute("step", step.toString());
56
- minInput.setAttribute("value", value.min.toString());
50
+ const minInput = dom.input("", {
51
+ type: "range",
52
+ name: `${id}-min`,
53
+ id: `${id}-min`,
54
+ min: min,
55
+ max: max,
56
+ step: step,
57
+ value: value.min,
58
+ });
57
59
  minInput.addEventListener("input", inputHandler);
58
60
  minInput.addEventListener("change", changeHandler);
59
- const maxInput = document.createElement("input");
60
- maxInput.setAttribute("type", "range");
61
- maxInput.setAttribute("name", `${id}-max`);
62
- maxInput.setAttribute("id", `${id}-max`);
63
- maxInput.setAttribute("min", min.toString());
64
- maxInput.setAttribute("max", max.toString());
65
- maxInput.setAttribute("step", step.toString());
66
- maxInput.setAttribute("value", value.max.toString());
61
+ const maxInput = dom.input("", {
62
+ type: "range",
63
+ name: `${id}-max`,
64
+ id: `${id}-max`,
65
+ min: min,
66
+ max: max,
67
+ step: step,
68
+ value: value.min,
69
+ });
67
70
  maxInput.addEventListener("input", inputHandler);
68
71
  maxInput.addEventListener("change", changeHandler);
69
- const inputWrapper = document.createElement("div");
70
- inputWrapper.classList.add("dual-range-input");
71
- inputWrapper.appendChild(minInput);
72
- inputWrapper.appendChild(maxInput);
73
- const right = document.createElement("div");
74
- right.classList.add("ctrls__control-right");
75
- right.appendChild(inputWrapper);
76
- const label = document.createElement("span");
77
- label.textContent = this.label;
78
- label.classList.add("ctrls__control-label");
79
- const valueSpan = document.createElement("span");
80
- valueSpan.classList.add("ctrls__control-value");
81
- label.appendChild(valueSpan);
82
- const element = document.createElement("div");
83
- element.classList.add("ctrls__control", "ctrls__control--dual-range");
84
- element.appendChild(label);
85
- element.appendChild(right);
72
+ const inputWrapper = dom.div("dual-range-input", {
73
+ children: [minInput, maxInput],
74
+ });
75
+ const right = dom.div("ctrls__control-right", {
76
+ children: [inputWrapper],
77
+ });
78
+ const valueSpan = dom.span("ctrls__control-value");
79
+ const label = dom.span("ctrls__control-label", {
80
+ children: [this.label, valueSpan],
81
+ });
82
+ const element = dom.div("ctrls__control ctrls__control--dual-range", {
83
+ children: [label, right],
84
+ });
86
85
  return {
87
86
  element,
88
87
  valueSpan,
@@ -1,4 +1,4 @@
1
- import type { Ctrl, CtrlChangeHandler, CtrlItemType, ConfigFor } from "./types";
1
+ import type { ConfigFor, Ctrl, CtrlChangeHandler, CtrlItemType } from "./types";
2
2
  export type Easing = [number, number, number, number];
3
3
  export declare class EasingCtrl implements Ctrl<Easing> {
4
4
  type: CtrlItemType;
@@ -1,5 +1,6 @@
1
- import random from "../utils/random";
2
1
  import BezierEasing from "bezier-easing";
2
+ import { dom } from "../utils/dom";
3
+ import random from "../utils/random";
3
4
  import { toHtmlId } from "../utils/string-utils";
4
5
  const easings = {
5
6
  EASE: [0.25, 0.1, 0.25, 1],
@@ -53,36 +54,27 @@ export class EasingCtrl {
53
54
  this.buildUI = () => {
54
55
  const { value } = this;
55
56
  const id = toHtmlId(this.id);
56
- const line1 = document.createElementNS("http://www.w3.org/2000/svg", "line");
57
- line1.setAttribute("class", "ctrls__easing-line ctrls__easing-line--1");
58
- line1.setAttribute("x1", "0");
59
- line1.setAttribute("y1", `${h}`);
60
- const line2 = document.createElementNS("http://www.w3.org/2000/svg", "line");
61
- line2.setAttribute("class", "ctrls__easing-line ctrls__easing-line--2");
62
- line2.setAttribute("x1", `${w}`);
63
- line2.setAttribute("y1", "0");
64
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
65
- path.setAttribute("class", "ctrls__easing-path");
66
- const borders = document.createElementNS("http://www.w3.org/2000/svg", "path");
67
- borders.classList.add("easing-borders");
68
- borders.setAttribute("d", `M 0 0 h ${w} M 0 ${h} h ${w}`);
69
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
70
- svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
71
- svg.appendChild(borders);
72
- svg.appendChild(path);
73
- svg.appendChild(line1);
74
- svg.appendChild(line2);
75
- const handle1 = document.createElement("button");
76
- handle1.className = "ctrls__easing-handle ctrls__easing-handle--1";
77
- handle1.innerHTML = "";
78
- const handle2 = document.createElement("button");
79
- handle2.className = "ctrls__easing-handle ctrls__easing-handle--2";
80
- handle2.innerHTML = "";
81
- const control = document.createElement("div");
82
- control.className = "ctrls__easing";
83
- control.appendChild(svg);
84
- control.appendChild(handle1);
85
- control.appendChild(handle2);
57
+ const line1 = dom.line("ctrls__easing-line ctrls__easing-line--1", {
58
+ x1: 0,
59
+ y1: h,
60
+ });
61
+ const line2 = dom.line("ctrls__easing-line ctrls__easing-line--2", {
62
+ x1: w,
63
+ y1: 0,
64
+ });
65
+ const path = dom.path("ctrls__easing-path");
66
+ const borders = dom.path("easing-borders", {
67
+ d: `M 0 0 h ${w} M 0 ${h} h ${w}`,
68
+ });
69
+ const svg = dom.svg("", {
70
+ viewBox: `0 0 ${w} ${h}`,
71
+ children: [borders, path, line1, line2],
72
+ });
73
+ const handle1 = dom.button("ctrls__easing-handle ctrls__easing-handle--1");
74
+ const handle2 = dom.button("ctrls__easing-handle ctrls__easing-handle--2");
75
+ const control = dom.div("ctrls__easing", {
76
+ children: [svg, handle1, handle2],
77
+ });
86
78
  const addListeners = (handle, index) => {
87
79
  let dragging = false;
88
80
  let dragStart = { x: 0, y: 0 };
@@ -185,51 +177,51 @@ export class EasingCtrl {
185
177
  // Presets
186
178
  let presetButtons = null;
187
179
  if (Object.keys(this.presets).length > 0) {
188
- presetButtons = document.createElement("div");
189
- presetButtons.classList.add("ctrls__easing-buttons");
190
- for (const key of Object.keys(this.presets)) {
191
- const button = document.createElement("button");
192
- button.textContent = key.toLowerCase().replace("ease_", "");
180
+ const buttons = Object.keys(this.presets).map((key) => {
181
+ const button = dom.button("", {
182
+ // Remove EASE_ prefix and make it lowercase
183
+ // EASE_IN -> in
184
+ children: [key.toLowerCase().replace("ease_", "")],
185
+ });
193
186
  button.addEventListener("click", () => {
194
187
  this.value = this.presets[key];
195
188
  this.onChange(this);
196
189
  this.update();
197
190
  });
198
- presetButtons.appendChild(button);
199
- }
191
+ return button;
192
+ });
193
+ presetButtons = dom.div("ctrls__easing-buttons", {
194
+ children: buttons,
195
+ });
200
196
  }
201
- const ticks = document.createElementNS("http://www.w3.org/2000/svg", "svg");
202
- ticks.classList.add("ctrls__easing-ticks");
203
- ticks.setAttribute("viewBox", `0 0 ${w} 5`);
204
- ticks.setAttribute("preserveAspectRatio", "none");
205
197
  const tickCount = 30;
206
198
  const ticksElements = [];
207
199
  for (let i = 0; i < tickCount; i++) {
208
- const tick = document.createElementNS("http://www.w3.org/2000/svg", "line");
209
- tick.setAttribute("y1", "0");
210
- tick.setAttribute("y2", "5");
200
+ const tick = dom.line("", {
201
+ y1: 0,
202
+ y2: 5,
203
+ });
211
204
  ticksElements.push(tick);
212
- ticks.appendChild(tick);
213
205
  }
214
- const controlWrapper = document.createElement("div");
215
- controlWrapper.classList.add("ctrls__easing-wrapper");
216
- controlWrapper.appendChild(ticks);
217
- controlWrapper.appendChild(control);
218
- controlWrapper.setAttribute("id", id);
219
- const right = document.createElement("div");
220
- right.classList.add("ctrls__control-right");
221
- right.appendChild(controlWrapper);
222
- if (presetButtons) {
223
- right.appendChild(presetButtons);
224
- }
225
- const label = document.createElement("span");
226
- label.textContent = this.label;
227
- label.classList.add("ctrls__control-label");
228
- const element = document.createElement("div");
229
- element.classList.add("ctrls__control", "ctrls__control--easing");
230
- element.setAttribute("data-value", value.join(","));
231
- element.appendChild(label);
232
- element.appendChild(right);
206
+ const ticks = dom.svg("ctrls__easing-ticks", {
207
+ viewBox: `0 0 ${w} 5`,
208
+ preserveAspectRatio: "none",
209
+ children: ticksElements,
210
+ });
211
+ const controlWrapper = dom.div("ctrls__easing-wrapper", {
212
+ id,
213
+ children: [ticks, control],
214
+ });
215
+ const right = dom.div("ctrls__control-right", {
216
+ children: [controlWrapper, presetButtons],
217
+ });
218
+ const label = dom.div("ctrls__control-label", {
219
+ children: [this.label],
220
+ });
221
+ const element = dom.div("ctrls__control ctrls__control--easing", {
222
+ "data-value": value.join(","),
223
+ children: [label, right],
224
+ });
233
225
  return {
234
226
  element,
235
227
  ticks: ticksElements,
@@ -1,6 +1,6 @@
1
+ import { dom } from "../utils/dom";
2
+ import { deleteIcon } from "../utils/icons";
1
3
  import { toHtmlId } from "../utils/string-utils";
2
- import { dom } from "./dom";
3
- import { closeIcon } from "../utils/icons";
4
4
  const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp", "svg+xml"];
5
5
  export class FileCtrl {
6
6
  constructor(config, onChange, onInput) {
@@ -17,13 +17,18 @@ export class FileCtrl {
17
17
  this.onChange(this);
18
18
  this.onInput(this);
19
19
  });
20
- const fakeInput = dom.label("ctrls__file-fake-input ctrls__btn ctrls__btn--sm", { for: id });
21
- fakeInput.innerHTML = "select file";
20
+ const fakeInput = dom.label("ctrls__file-fake-input ctrls__btn ctrls__btn--sm", {
21
+ for: id,
22
+ children: ["select file"],
23
+ });
22
24
  if (this.accept) {
23
- fakeInput.innerHTML += `<span>${this.accept}</span>`;
25
+ const acceptSpan = dom.span("", { children: [this.accept] });
26
+ fakeInput.append(acceptSpan);
24
27
  }
25
- const clearButton = dom.button("ctrls__file-clear ctrls__btn");
26
- clearButton.innerHTML = closeIcon;
28
+ const clearButton = dom.button("ctrls__file-clear ctrls__btn", {
29
+ innerHTML: deleteIcon,
30
+ "aria-label": "Clear file",
31
+ });
27
32
  clearButton.addEventListener("click", () => {
28
33
  if (input.files) {
29
34
  this.update(null);
@@ -31,21 +36,20 @@ export class FileCtrl {
31
36
  this.onInput(this);
32
37
  }
33
38
  });
34
- const top = dom.div("ctrls__file-top");
35
- top.append(input);
36
- top.append(fakeInput);
37
- top.append(clearButton);
39
+ const top = dom.div("ctrls__file-top", {
40
+ children: [input, fakeInput, clearButton],
41
+ });
38
42
  const preview = dom.div("ctrls__file-preview");
39
- const right = dom.div("ctrls__control-right");
40
- right.append(top);
41
- right.append(preview);
43
+ const right = dom.div("ctrls__control-right", {
44
+ children: [top, preview],
45
+ });
42
46
  const label = dom.label("ctrls__control-label", {
43
47
  for: id,
48
+ children: [this.label],
49
+ });
50
+ const element = dom.div("ctrls__control ctrls__control--file", {
51
+ children: [label, right],
44
52
  });
45
- label.textContent = this.label;
46
- const element = dom.div("ctrls__control ctrls__control--file");
47
- element.appendChild(label);
48
- element.appendChild(right);
49
53
  return {
50
54
  element,
51
55
  input,
@@ -55,20 +59,19 @@ export class FileCtrl {
55
59
  this.update = (file) => {
56
60
  this.value = file;
57
61
  if (file) {
58
- const item = document.createElement("figure");
59
- item.classList.add("ctrls__file-preview-item");
60
- if (IMAGE_EXTENSIONS.includes(file.type.split("/")[1])) {
61
- console.log(URL.createObjectURL(file), file);
62
- const img = document.createElement("img");
63
- img.src = URL.createObjectURL(file);
64
- img.alt = file.name;
65
- img.classList.add("ctrls__file-image");
66
- item.appendChild(img);
67
- }
68
- const label = document.createElement("figcaption");
69
- label.classList.add("ctrls__file-label");
70
- label.textContent = file.name;
71
- item.appendChild(label);
62
+ const isImage = IMAGE_EXTENSIONS.includes(file.type.split("/")[1]);
63
+ const img = isImage
64
+ ? dom.img("ctrls__file-image", {
65
+ src: URL.createObjectURL(file),
66
+ alt: file.name,
67
+ })
68
+ : "";
69
+ const label = dom.figcaption("ctrls__file-label", {
70
+ children: [file.name],
71
+ });
72
+ const item = dom.figure("ctrls__file-preview-item", {
73
+ children: [img, label],
74
+ });
72
75
  this.preview.replaceChildren(item);
73
76
  }
74
77
  else {
@@ -1,13 +1,13 @@
1
+ import { dom } from "../utils/dom";
1
2
  export const getHTMLControlElement = (config) => {
2
- const right = document.createElement("div");
3
- right.classList.add("ctrls__control-right");
4
- right.append(config.html);
5
- const label = document.createElement("label");
6
- label.textContent = config.label || config.name;
7
- label.classList.add("ctrls__control-label");
8
- const element = document.createElement("div");
9
- element.classList.add("ctrls__control", "ctrls__control--seed");
10
- element.appendChild(label);
11
- element.appendChild(right);
3
+ const right = dom.div("ctrls__control-right", {
4
+ children: [config.html],
5
+ });
6
+ const label = dom.label("ctrls__control-label", {
7
+ children: [config.label || config.name],
8
+ });
9
+ const element = dom.div("ctrls__control ctrls__control--seed", {
10
+ children: [label, right],
11
+ });
12
12
  return element;
13
13
  };
@@ -1,6 +1,7 @@
1
1
  import random from "../utils/random";
2
2
  import { toKebabCase } from "../utils/string-utils";
3
3
  import { getRandomString } from "../utils/get-random-string";
4
+ import { dom } from "../utils/dom";
4
5
  export class RadioCtrl {
5
6
  constructor(config, onChange, onInput) {
6
7
  this.type = "radio";
@@ -21,11 +22,12 @@ export class RadioCtrl {
21
22
  this.buildUI = () => {
22
23
  const { items, value } = this;
23
24
  const inputs = items.map((item) => {
24
- const input = document.createElement("input");
25
- input.setAttribute("type", "radio");
26
- input.setAttribute("name", this.htmlId);
27
- input.setAttribute("id", `${this.htmlId}-${toKebabCase(item.value)}`);
28
- input.setAttribute("value", item.value);
25
+ const input = dom.input("", {
26
+ type: "radio",
27
+ name: this.htmlId,
28
+ id: `${this.htmlId}-${toKebabCase(item.value)}`,
29
+ value: item.value,
30
+ });
29
31
  input.checked = item.value === value;
30
32
  input.addEventListener("change", () => {
31
33
  this.value = this.parse(input.value);
@@ -35,25 +37,24 @@ export class RadioCtrl {
35
37
  this.value = this.parse(input.value);
36
38
  this.onInput(this);
37
39
  });
38
- const label = document.createElement("span");
39
- label.textContent = item.label;
40
- const option = document.createElement("label");
41
- option.classList.add("ctrls__radio-label");
42
- option.appendChild(input);
43
- option.appendChild(label);
40
+ const label = dom.span("", {
41
+ children: [item.label],
42
+ });
43
+ const option = dom.label("ctrls__radio-label", {
44
+ children: [input, label],
45
+ });
44
46
  return option;
45
47
  });
46
- const right = document.createElement("div");
47
- right.classList.add("ctrls__control-right");
48
- right.style.gridTemplateColumns = `repeat(${this.columns}, 1fr)`;
49
- right.append(...inputs);
50
- const label = document.createElement("span");
51
- label.textContent = this.label;
52
- label.classList.add("ctrls__control-label");
53
- const element = document.createElement("div");
54
- element.classList.add("ctrls__control", "ctrls__control--radio");
55
- element.appendChild(label);
56
- element.appendChild(right);
48
+ const right = dom.div("ctrls__control-right", {
49
+ children: inputs,
50
+ style: `grid-template-columns: repeat(${this.columns}, 1fr);`,
51
+ });
52
+ const label = dom.span("ctrls__control-label", {
53
+ children: [this.label],
54
+ });
55
+ const element = dom.div("ctrls__control ctrls__control--radio", {
56
+ children: [label, right],
57
+ });
57
58
  return element;
58
59
  };
59
60
  this.update = (value) => {
@@ -69,10 +70,10 @@ export class RadioCtrl {
69
70
  next.checked = true;
70
71
  };
71
72
  this.items = [];
72
- Object.keys(config.items).forEach((key) => {
73
+ Object.entries(config.items).forEach(([label, value]) => {
73
74
  this.items.push({
74
- label: key,
75
- value: config.items[key],
75
+ label,
76
+ value,
76
77
  });
77
78
  });
78
79
  this.columns = config.columns || 3;
@@ -1,6 +1,7 @@
1
1
  import random from "../utils/random";
2
2
  import { roundToStep } from "../utils/round-to-step";
3
3
  import { toHtmlId } from "../utils/string-utils";
4
+ import { dom } from "../utils/dom";
4
5
  export class RangeCtrl {
5
6
  constructor(config, onChange, onInput) {
6
7
  this.type = "range";
@@ -21,15 +22,15 @@ export class RangeCtrl {
21
22
  this.buildUI = () => {
22
23
  const { min, max, step, value } = this;
23
24
  const id = toHtmlId(this.id);
24
- const input = document.createElement("input");
25
- input.classList.add("ctrls__range-input");
26
- input.setAttribute("type", "range");
27
- input.setAttribute("id", id);
28
- input.setAttribute("name", id);
29
- input.setAttribute("min", min.toString());
30
- input.setAttribute("max", max.toString());
31
- input.setAttribute("step", step.toString());
32
- input.setAttribute("value", value.toString());
25
+ const input = dom.input("ctrls__range-input", {
26
+ type: "range",
27
+ id,
28
+ name: id,
29
+ min,
30
+ max,
31
+ step,
32
+ value,
33
+ });
33
34
  input.addEventListener("input", () => {
34
35
  this.value = this.parse(input.value);
35
36
  this.update(this.value);
@@ -45,19 +46,16 @@ export class RangeCtrl {
45
46
  const percentage = ((value - min) / (max - min)) * 100;
46
47
  this.element.style.setProperty("--gradient-position", `${percentage.toFixed(2)}%`);
47
48
  });
48
- const right = document.createElement("div");
49
- right.classList.add("ctrls__control-right");
50
- right.append(input);
51
- const label = document.createElement("span");
52
- label.textContent = this.label;
53
- label.classList.add("ctrls__control-label");
54
- const valueSpan = document.createElement("span");
55
- valueSpan.classList.add("ctrls__control-value");
56
- label.appendChild(valueSpan);
57
- const element = document.createElement("label");
58
- element.classList.add("ctrls__control", "ctrls__control--range");
59
- element.appendChild(label);
60
- element.appendChild(right);
49
+ const right = dom.div("ctrls__control-right", {
50
+ children: [input],
51
+ });
52
+ const valueSpan = dom.span("ctrls__control-value");
53
+ const label = dom.span("ctrls__control-label", {
54
+ children: [this.label, valueSpan],
55
+ });
56
+ const element = dom.label("ctrls__control ctrls__control--range", {
57
+ children: [label, right],
58
+ });
61
59
  return {
62
60
  element,
63
61
  input,
@@ -1,6 +1,7 @@
1
1
  import generateSeed from "../utils/generate-seed";
2
2
  import { refreshIcon } from "../utils/icons";
3
3
  import { toHtmlId } from "../utils/string-utils";
4
+ import { dom } from "../utils/dom";
4
5
  export class SeedCtrl {
5
6
  constructor(config, onChange, onInput) {
6
7
  this.type = "seed";
@@ -22,12 +23,12 @@ export class SeedCtrl {
22
23
  this.buildUI = () => {
23
24
  const { value } = this;
24
25
  const id = toHtmlId(this.id);
25
- const input = document.createElement("input");
26
- input.classList.add("ctrls__seed-input");
27
- input.setAttribute("type", "text");
28
- input.setAttribute("value", value.toString());
29
- input.setAttribute("id", id);
30
- input.setAttribute("name", id);
26
+ const input = dom.input("ctrls__seed-input", {
27
+ type: "text",
28
+ value,
29
+ id,
30
+ name: id,
31
+ });
31
32
  input.addEventListener("change", () => {
32
33
  this.value = this.parse(input.value);
33
34
  this.onChange(this);
@@ -36,26 +37,24 @@ export class SeedCtrl {
36
37
  this.value = this.parse(input.value);
37
38
  this.onInput(this);
38
39
  });
39
- const reload = document.createElement("button");
40
- reload.innerHTML = refreshIcon;
41
- reload.classList.add("ctrls__seed-new-button", "ctrls__btn");
40
+ const reload = dom.button("ctrls__seed-new-button ctrls__btn", {
41
+ innerHTML: refreshIcon,
42
+ });
42
43
  reload.addEventListener("click", () => {
43
44
  this.value = this.getRandomValue();
44
45
  this.update();
45
46
  this.onChange(this);
46
47
  });
47
- const right = document.createElement("div");
48
- right.classList.add("ctrls__control-right");
49
- right.append(input);
50
- right.append(reload);
51
- const label = document.createElement("label");
52
- label.textContent = this.label;
53
- label.setAttribute("for", id);
54
- label.classList.add("ctrls__control-label");
55
- const element = document.createElement("div");
56
- element.classList.add("ctrls__control", "ctrls__control--seed");
57
- element.appendChild(label);
58
- element.appendChild(right);
48
+ const right = dom.div("ctrls__control-right", {
49
+ children: [input, reload],
50
+ });
51
+ const label = dom.label("ctrls__control-label", {
52
+ for: id,
53
+ children: [this.label],
54
+ });
55
+ const element = dom.div("ctrls__control ctrls__control--seed", {
56
+ children: [label, right],
57
+ });
59
58
  return {
60
59
  element,
61
60
  input,
@@ -10,7 +10,6 @@ export declare class Ctrls<Configs extends readonly ConfigItem[]> {
10
10
  constructor(configs: Configs, options?: ControlsOptions);
11
11
  processControls: (configs: Configs) => HTMLElement[];
12
12
  registerControl: (config: TypedControlConfig, onChangeControlHandler: (control: CtrlComponent) => void, onInputControlHandler: (control: CtrlComponent) => void, group?: string) => CtrlComponent;
13
- toggleVisibility: () => void;
14
13
  addHashListeners: () => void;
15
14
  getHash: () => string;
16
15
  setHash: () => void;
@@ -11,6 +11,8 @@ import { FileCtrl } from "./ctrl-file";
11
11
  import Alea from "../utils/alea";
12
12
  import { chevronUpIcon, diceIcon } from "../utils/icons";
13
13
  import { getHTMLControlElement } from "./ctrl-html";
14
+ import { dom } from "../utils/dom";
15
+ import { getDrawer } from "../utils/get-drawer";
14
16
  const controlMap = {
15
17
  boolean: BooleanCtrl,
16
18
  range: RangeCtrl,
@@ -41,25 +43,18 @@ export class Ctrls {
41
43
  // Processing configs and creating component instances and HTML elements
42
44
  configs.map((config) => {
43
45
  if (config.type === "group") {
44
- // Create group element
45
- const groupElement = document.createElement("div");
46
- groupElement.classList.add("ctrls__group");
47
- if (config.isCollapsed) {
48
- groupElement.classList.add("ctrls__group--hidden");
49
- }
50
- const groupTitle = document.createElement("button");
51
- groupTitle.classList.add("ctrls__group-title");
52
- groupTitle.innerHTML =
53
- (config.label || toSpaceCase(config.name)) + chevronUpIcon;
54
- groupTitle.addEventListener("click", () => {
55
- groupTitle.parentElement?.classList.toggle("ctrls__group--hidden");
46
+ // Group title
47
+ const groupTitle = dom.button("ctrls__group-title", {
48
+ innerHTML: (config.label || toSpaceCase(config.name)) + chevronUpIcon,
56
49
  });
57
- // Add title
58
- groupElement.append(groupTitle);
59
- config.controls.forEach((itemConfig) => {
50
+ const controlsElements = config.controls.map((itemConfig) => {
60
51
  const control = this.registerControl(itemConfig, onChangeControlHandler, onInputControlHandler, toCamelCase(config.name));
61
- // Add the control elements to the group element
62
- groupElement.append(control?.element);
52
+ return control.element;
53
+ });
54
+ const groupControls = getDrawer("ctrls__group-controls", controlsElements, groupTitle, config.isCollapsed);
55
+ // Create group element
56
+ const groupElement = dom.div("ctrls__group", {
57
+ children: [groupTitle, groupControls],
63
58
  });
64
59
  // Add the group element
65
60
  elements.push(groupElement);
@@ -95,9 +90,6 @@ export class Ctrls {
95
90
  this.controls.push(control);
96
91
  return control;
97
92
  };
98
- this.toggleVisibility = () => {
99
- this.element.classList.toggle("ctrls--hidden");
100
- };
101
93
  this.addHashListeners = () => {
102
94
  window.addEventListener("hashchange", this.updateFromHash);
103
95
  // Update all inputs using initial values from the hash
@@ -172,33 +164,29 @@ export class Ctrls {
172
164
  theme: "system",
173
165
  ...options,
174
166
  };
175
- // Main element
176
- this.element = document.createElement("div");
177
- this.element.classList.add("ctrls");
178
- this.element.classList.add(`ctrls--${this.options.theme}-theme`);
179
167
  // Title
180
- if (this.options.title) {
181
- const titleButton = document.createElement("button");
182
- titleButton.classList.add("ctrls__title");
183
- titleButton.innerHTML = this.options.title + chevronUpIcon;
184
- titleButton.addEventListener("click", this.toggleVisibility);
185
- this.element.appendChild(titleButton);
186
- }
187
- // Controls wrapper
188
- const controlsContainer = document.createElement("div");
189
- controlsContainer.classList.add("ctrls__controls");
190
- this.element.appendChild(controlsContainer);
191
- // Controls
192
- const controlElements = this.processControls(configs);
193
- controlsContainer.append(...controlElements);
168
+ let titleButton = this.options.title
169
+ ? dom.button("ctrls__title", {
170
+ innerHTML: this.options.title + chevronUpIcon,
171
+ })
172
+ : null;
194
173
  // Randomize button
174
+ let randomizeRow = null;
195
175
  if (this.options.showRandomizeButton) {
196
- const randomizeButton = document.createElement("button");
197
- randomizeButton.classList.add("ctrls__randomize", "ctrls__btn", "ctrls__btn--lg");
198
- randomizeButton.innerHTML = `Randomize ${diceIcon}`;
176
+ const randomizeButton = dom.button("ctrls__randomize ctrls__btn ctrls__btn--lg", { innerHTML: `Randomize ${diceIcon}` });
199
177
  randomizeButton.addEventListener("click", this.randomize);
200
- controlsContainer.appendChild(randomizeButton);
178
+ randomizeRow = dom.div("ctrls__control-no-label", {
179
+ children: [randomizeButton],
180
+ });
201
181
  }
182
+ // Control elements
183
+ const controlElements = this.processControls(configs);
184
+ // Controls wrapper
185
+ const controlsContainer = getDrawer("ctrls__controls", [...controlElements, randomizeRow], titleButton);
186
+ // Main element
187
+ this.element = dom.div(`ctrls ctrls--${this.options.theme}-theme`, {
188
+ children: [titleButton, controlsContainer],
189
+ });
202
190
  // Append the Ctrls element to the provided parent element
203
191
  if (this.options.parent) {
204
192
  this.options.parent.appendChild(this.element);
package/dist/ctrls.css CHANGED
@@ -346,25 +346,43 @@
346
346
  width: 1rem;
347
347
  }
348
348
 
349
- .ctrls__group-title {
350
- padding-block: 0.125rem;
349
+ .ctrls__controls-inner {
350
+ overflow: auto;
351
+ scrollbar-width: thin;
352
+ scrollbar-color: var(--ctrls-scrollbar-thumb-bg) transparent;
353
+ }
354
+ .ctrls__controls-inner > *:last-child:not(:first-child) {
355
+ margin-bottom: 0.25rem;
351
356
  }
352
357
 
353
- .ctrls--hidden .ctrls__title svg,
354
- .ctrls__group--hidden .ctrls__group-title svg {
355
- transform: rotate(180deg);
358
+ .ctrls__drawer {
359
+ display: none;
360
+ align-items: flex-start;
361
+ grid-template-rows: 0fr;
362
+ transition: grid-template-rows 500ms, opacity 500ms, display 500ms;
363
+ transition-behavior: allow-discrete;
364
+ overflow: hidden;
365
+ opacity: 0;
356
366
  }
357
367
 
358
- .ctrls__controls {
359
- overflow: auto;
368
+ .ctrls__drawer--expanded {
360
369
  display: grid;
361
- padding: 0.25rem 0;
362
- scrollbar-width: thin;
363
- scrollbar-color: var(--ctrls-scrollbar-thumb-bg) transparent;
370
+ grid-template-rows: 1fr;
371
+ opacity: 1;
364
372
  }
365
373
 
366
- .ctrls--hidden .ctrls__controls {
367
- display: none;
374
+ @starting-style {
375
+ .ctrls__drawer--ready.ctrls__drawer--expanded {
376
+ grid-template-rows: 0fr;
377
+ opacity: 0;
378
+ }
379
+ }
380
+ .ctrls__drawer-inner {
381
+ overflow: hidden;
382
+ }
383
+
384
+ .ctrls__drawer-toggle--collapsed svg {
385
+ transform: rotate(180deg);
368
386
  }
369
387
 
370
388
  .ctrls__group {
@@ -373,19 +391,14 @@
373
391
  border-radius: var(--ctrls-radius);
374
392
  overflow: hidden;
375
393
  }
394
+ .ctrls__group:only-child {
395
+ margin-block: 0.5rem;
396
+ }
376
397
 
377
398
  .ctrls__group .ctrls__control {
378
399
  grid-template-columns: calc(var(--ctrls-label-width) - 0.5rem) minmax(0, 1fr);
379
400
  }
380
401
 
381
- .ctrls__group--hidden {
382
- padding-block-end: 0;
383
- }
384
-
385
- .ctrls__group--hidden .ctrls__control {
386
- display: none;
387
- }
388
-
389
402
  .ctrls__control {
390
403
  padding: 0.25rem 0.5rem;
391
404
  display: grid;
@@ -398,6 +411,13 @@
398
411
  .ctrls__control:hover .ctrls__control-label {
399
412
  color: var(--ctrls-theme);
400
413
  }
414
+ .ctrls__control:only-child {
415
+ padding-block: 0.5rem;
416
+ }
417
+
418
+ .ctrls__control-no-label {
419
+ padding: 0.25rem 0.5rem 0.25rem calc(var(--ctrls-label-width) + 0.5rem);
420
+ }
401
421
 
402
422
  .ctrls__control-label {
403
423
  color: var(--ctrls-text-muted);
@@ -446,8 +466,7 @@
446
466
  }
447
467
 
448
468
  .ctrls__btn--lg {
449
- margin-inline-start: calc(var(--ctrls-label-width) + 0.5rem);
450
- margin-inline-end: 0.5rem;
469
+ width: 100%;
451
470
  padding: 0.5rem 1rem;
452
471
  background: var(--ctrls-btn-bg);
453
472
  border: 1px solid var(--ctrls-input-border);
@@ -460,7 +479,7 @@
460
479
  }
461
480
 
462
481
  .ctrls__randomize {
463
- margin-block: 0.25rem;
482
+ grid-column: 2;
464
483
  }
465
484
  .ctrls__randomize:focus-visible svg, .ctrls__randomize:hover svg {
466
485
  transform: rotate(0.5turn);
@@ -776,7 +795,6 @@ label:hover .ctrls__boolean-input:not(:checked) + .ctrls__boolean-checkmark {
776
795
  border: 1px solid var(--ctrls-input-border);
777
796
  background: var(--ctrls-input-wrapper-bg);
778
797
  border-radius: min(var(--ctrls-radius), 12px);
779
- margin-bottom: 0.25rem;
780
798
  }
781
799
 
782
800
  .ctrls__easing {
@@ -820,6 +838,7 @@ label:hover .ctrls__boolean-input:not(:checked) + .ctrls__boolean-checkmark {
820
838
  display: grid;
821
839
  grid-template-columns: 1fr 1fr 1fr;
822
840
  gap: 0.25rem;
841
+ margin-top: 0.25rem;
823
842
  }
824
843
 
825
844
  .ctrls__easing-buttons button {
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["../node_modules/@stanko/dual-range-input/dist/index.css","../src/scss/_ctrls.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;;AC3IF;EACE;AAAA;AAAA;EAGA;EACA;EACA;EACA;EACA;EAGA;EACA;EAEA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EAGA;EAGA;EACA;EACA;EACA;EAGA;EACA;EACA;EAGA;EAGA;EAEA;EACA;EAEA;EACA;EAGA;EAGA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;EAGA;EACA;EAEA;EAEA;EACA;EAEA;EAGA;EAEA;EACA;;;AAgDF;EACE;IA5CA;IAEA;IACA;IAGA;IACA;IACA;IACA;IACA;IACA;IAGA;IAGA;IACA;IACA;IACA;IAGA;IACA;IACA;IAGA;IAGA;IACA;IAEA;IAGA;IAGA;;;AASF;EAjDE;EAEA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EAGA;EAGA;EACA;EACA;EACA;EAGA;EACA;EACA;EAGA;EAGA;EACA;EAEA;EAGA;EAGA;;;AAeF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EAGE;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;;;AAKF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;;AAGF;AAAA;AAAA;EAEE;EACA;;AAKF;AAAA;EACE;EACA;EACA;;AAGF;AAAA;EACE;EACA;;;AAIJ;EACE;;;AAKA;AAAA;EACE;;;AAMJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAKF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAKF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;;AAKN;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAMF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;;AAIF;EAEE;;AAGF;EACE;EACA;EACA,YACE;;AAIJ;EACE;;;AAKJ;EACE;EACA;EACA;;AAEA;EAEE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;;;AAIJ;EACE;;AAIE;EACE;;AAGF;AAAA;AAAA;EAEE;;;AAON;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;;;AAIF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAmCJ;EA5BE;EACA;EACA;EACA;EACA;EAEA;;;AA0BF;EAhCE;EACA;EACA;EACA;EACA;EAEA;;;AA8BF;EAtBE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAkBA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAKF;EAvDE;EACA;EACA;EACA;EACA;EAEA;;;AAqDF;EA7CE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAwCA;EACA;;;AAGF;EACE;EACA;;;AAKF;EAIE;;AAEA;EACE;;AAPJ;EAUE;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;;;AAMJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;EAGF;EACA;EACA;;AAEA;EAEE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAKF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;;AAIE;EACE;;;AAON;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;;AAKA;AAAA;AAAA;EAEE;EACA;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAKF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;AAAA;EAEE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;;AAIF;EAEE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAEE;;AAEA;EACE;EACA","file":"ctrls.css"}
1
+ {"version":3,"sourceRoot":"","sources":["../node_modules/@stanko/dual-range-input/dist/index.css","../src/scss/_ctrls.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;;;;AC3IF;EACE;AAAA;AAAA;EAGA;EACA;EACA;EACA;EACA;EAGA;EACA;EAEA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EAGA;EAGA;EACA;EACA;EACA;EAGA;EACA;EACA;EAGA;EAGA;EAEA;EACA;EAEA;EACA;EAGA;EAGA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;EAGA;EACA;EAEA;EAEA;EACA;EAEA;EAGA;EAEA;EACA;;;AAgDF;EACE;IA5CA;IAEA;IACA;IAGA;IACA;IACA;IACA;IACA;IACA;IAGA;IAGA;IACA;IACA;IACA;IAGA;IACA;IACA;IAGA;IAGA;IACA;IAEA;IAGA;IAGA;;;AASF;EAjDE;EAEA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EAGA;EAGA;EACA;EACA;EACA;EAGA;EACA;EACA;EAGA;EAGA;EACA;EAEA;EAGA;EAGA;;;AAeF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EAGE;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;;;AAKF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;;AAGF;AAAA;AAAA;EAEE;EACA;;AAKF;AAAA;EACE;EACA;EACA;;AAGF;AAAA;EACE;EACA;;;AAMJ;EACE;EACA;EACA;;AAIA;EACE;;;AAMJ;EACE;EACA;EACA;EACA,YACE;EAGF;EACA;EAEA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;IACE;IACA;;;AAIJ;EACE;;;AAIA;EACE;;;AAMJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAKF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAMF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;;AAIF;EAEE;;AAGF;EACE;EACA;EACA,YACE;;AAIJ;EACE;;;AAKJ;EACE;EACA;EACA;;AAEA;EAEE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;;;AAIJ;EACE;;AAIE;EACE;;AAGF;AAAA;AAAA;EAEE;;;AAON;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;;;AAIF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAmCJ;EA5BE;EACA;EACA;EACA;EACA;EAEA;;;AA0BF;EAhCE;EACA;EACA;EACA;EACA;EAEA;;;AA8BF;EAtBE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAkBA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAKF;EAvDE;EACA;EACA;EACA;EACA;EAEA;;;AAqDF;EA7CE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAwCA;EACA;;;AAGF;EACE;EACA;;;AAKF;EAIE;;AAEA;EACE;;AAPJ;EAUE;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;;;AAMJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;EAGF;EACA;EACA;;AAEA;EAEE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAKF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;;AAIE;EACE;;;AAON;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;;AAKA;AAAA;AAAA;EAEE;EACA;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAKF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;AAAA;EAEE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;;AAIF;EAEE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAEE;;AAEA;EACE;EACA","file":"ctrls.css"}
@@ -0,0 +1,28 @@
1
+ type Attrs = {
2
+ [key: string]: unknown;
3
+ for?: string;
4
+ children?: (HTMLElement | SVGSVGElement | string | undefined | null)[];
5
+ innerHTML?: string;
6
+ };
7
+ type SVGAttrs = {
8
+ [key: string]: unknown;
9
+ for?: string;
10
+ children?: (SVGElement | string)[];
11
+ innerHTML?: string;
12
+ };
13
+ export declare const dom: {
14
+ div: (className?: string, attrs?: Attrs) => HTMLDivElement;
15
+ span: (className?: string, attrs?: Attrs) => HTMLSpanElement;
16
+ input: (className?: string, attrs?: Attrs) => HTMLInputElement;
17
+ label: (className?: string, attrs?: Attrs) => HTMLLabelElement;
18
+ button: (className?: string, attrs?: Attrs) => HTMLButtonElement;
19
+ img: (className?: string, attrs?: Attrs) => HTMLImageElement;
20
+ figure: (className?: string, attrs?: Attrs) => HTMLElement;
21
+ figcaption: (className?: string, attrs?: Attrs) => HTMLElement;
22
+ svg: (className?: string, attrs?: SVGAttrs) => SVGSVGElement;
23
+ path: (className?: string, attrs?: SVGAttrs) => SVGPathElement;
24
+ line: (className?: string, attrs?: SVGAttrs) => SVGLineElement;
25
+ circle: (className?: string, attrs?: SVGAttrs) => SVGCircleElement;
26
+ rect: (className?: string, attrs?: SVGAttrs) => SVGRectElement;
27
+ };
28
+ export {};
@@ -0,0 +1,61 @@
1
+ // ----- DOM ----- //
2
+ const el = (tag, className = "", attrs = {}) => {
3
+ const element = document.createElement(tag);
4
+ element.className = className;
5
+ // Add children and innerHTML and remove them from attrs
6
+ if (attrs.children) {
7
+ attrs.children
8
+ // Remove falsy children
9
+ .filter(Boolean)
10
+ .forEach((child) => {
11
+ element.append(child);
12
+ });
13
+ delete attrs.children;
14
+ }
15
+ else if (attrs.innerHTML) {
16
+ element.innerHTML = attrs.innerHTML;
17
+ delete attrs.innerHTML;
18
+ }
19
+ for (const [key, value] of Object.entries(attrs)) {
20
+ element.setAttribute(key, String(value));
21
+ }
22
+ return element;
23
+ };
24
+ // ----- SVG ----- //
25
+ const svgEl = (tag, className = "", attrs = {}) => {
26
+ const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
27
+ // className is deprecated for SVG elements
28
+ element.classList.add(...className.split(" ").filter(Boolean));
29
+ // Add children and innerHTML and remove them from attrs
30
+ if (attrs.children) {
31
+ attrs.children.forEach((child) => {
32
+ element.append(child);
33
+ });
34
+ delete attrs.children;
35
+ }
36
+ else if (attrs.innerHTML) {
37
+ element.innerHTML = attrs.innerHTML;
38
+ delete attrs.innerHTML;
39
+ }
40
+ for (const [key, value] of Object.entries(attrs)) {
41
+ element.setAttribute(key, String(value));
42
+ }
43
+ return element;
44
+ };
45
+ // ----- API ----- //
46
+ export const dom = {
47
+ div: (className = "", attrs = {}) => el("div", className, attrs),
48
+ span: (className = "", attrs = {}) => el("span", className, attrs),
49
+ input: (className = "", attrs = {}) => el("input", className, attrs),
50
+ label: (className = "", attrs = {}) => el("label", className, attrs),
51
+ button: (className = "", attrs = {}) => el("button", className, attrs),
52
+ img: (className = "", attrs = {}) => el("img", className, attrs),
53
+ figure: (className = "", attrs = {}) => el("figure", className, attrs),
54
+ figcaption: (className = "", attrs = {}) => el("figcaption", className, attrs),
55
+ // SVG elements
56
+ svg: (className = "", attrs = {}) => svgEl("svg", className, attrs),
57
+ path: (className = "", attrs = {}) => svgEl("path", className, attrs),
58
+ line: (className = "", attrs = {}) => svgEl("line", className, attrs),
59
+ circle: (className = "", attrs = {}) => svgEl("circle", className, attrs),
60
+ rect: (className = "", attrs = {}) => svgEl("rect", className, attrs),
61
+ };
@@ -0,0 +1 @@
1
+ export declare const getDrawer: (className?: string, children?: (HTMLElement | null)[], toggleButton?: HTMLButtonElement | null, isCollapsed?: boolean) => HTMLDivElement;
@@ -0,0 +1,22 @@
1
+ import { dom } from "./dom";
2
+ export const getDrawer = (className = "", children = [], toggleButton, isCollapsed = false) => {
3
+ const inner = dom.div(`ctrls__drawer-inner ${className}-inner`, {
4
+ children,
5
+ });
6
+ const outer = dom.div(`ctrls__drawer ${className}`, {
7
+ children: [inner],
8
+ });
9
+ if (isCollapsed) {
10
+ outer.classList.add("ctrls__drawer--collapsed");
11
+ }
12
+ else {
13
+ outer.classList.add("ctrls__drawer--expanded");
14
+ }
15
+ toggleButton?.addEventListener("click", () => {
16
+ outer.classList.add("ctrls__drawer--ready");
17
+ outer.classList.toggle("ctrls__drawer--collapsed");
18
+ outer.classList.toggle("ctrls__drawer--expanded");
19
+ toggleButton?.classList.toggle("ctrls__drawer-toggle--collapsed");
20
+ });
21
+ return outer;
22
+ };
@@ -1,5 +1,5 @@
1
1
  export declare const checkIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6 9 17l-5-5\"/></svg>";
2
2
  export declare const refreshIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5\"/><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/></svg>";
3
3
  export declare const diceIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" stroke-width=\"2\"/><path d=\"M16 8h.01\"/><path d=\"M8 8h.01\"/><path d=\"M8 16h.01\"/><path d=\"M16 16h.01\"/><path d=\"M12 12h.01\"/></svg>";
4
- export declare const closeIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\"/><path d=\"M3 6h18\"/><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>";
4
+ export declare const deleteIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\"/><path d=\"M3 6h18\"/><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>";
5
5
  export declare const chevronUpIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m18 15-6-6-6 6\"/></svg>";
@@ -2,5 +2,5 @@
2
2
  export const checkIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>`;
3
3
  export const refreshIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg>`;
4
4
  export const diceIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2" stroke-width="2"/><path d="M16 8h.01"/><path d="M8 8h.01"/><path d="M8 16h.01"/><path d="M16 16h.01"/><path d="M12 12h.01"/></svg>`;
5
- export const closeIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
5
+ export const deleteIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
6
6
  export const chevronUpIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stanko/ctrls",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "vitest",
@@ -10,7 +10,8 @@
10
10
  "build-lib": "rm -rf ./dist && tsc -p ./tsconfig-lib.json && npm run build-css",
11
11
  "parse-markdown": "node ./parse-markdown.js",
12
12
  "preview": "vite preview",
13
- "prepare": "cp ./pre-commit ./.git/hooks/pre-commit && chmod +x ./.git/hooks/pre-commit"
13
+ "prepare": "cp ./pre-commit ./.git/hooks/pre-commit && chmod +x ./.git/hooks/pre-commit",
14
+ "prepublish": "npm run build-lib"
14
15
  },
15
16
  "types": "./dist/ctrls/index.d.ts",
16
17
  "main": "./dist/ctrls/index.js",
@@ -1,12 +0,0 @@
1
- type Attrs = Record<string, string | number> & {
2
- for?: string;
3
- };
4
- export declare const dom: {
5
- el: <T extends keyof HTMLElementTagNameMap>(tag: T, className?: string, attrs?: Attrs) => HTMLElementTagNameMap[T];
6
- div: (className?: string, attrs?: Attrs) => HTMLDivElement;
7
- span: (className?: string, attrs?: Attrs) => HTMLSpanElement;
8
- input: (className?: string, attrs?: Attrs) => HTMLInputElement;
9
- label: (className?: string, attrs?: Attrs) => HTMLLabelElement;
10
- button: (className?: string, attrs?: Attrs) => HTMLButtonElement;
11
- };
12
- export {};
package/dist/ctrls/dom.js DELETED
@@ -1,16 +0,0 @@
1
- const el = (tag, className = "", attrs = {}) => {
2
- const element = document.createElement(tag);
3
- element.className = className;
4
- for (const [key, value] of Object.entries(attrs)) {
5
- element.setAttribute(key, value.toString());
6
- }
7
- return element;
8
- };
9
- export const dom = {
10
- el,
11
- div: (className = "", attrs = {}) => el("div", className, attrs),
12
- span: (className = "", attrs = {}) => el("span", className, attrs),
13
- input: (className = "", attrs = {}) => el("input", className, attrs),
14
- label: (className = "", attrs = {}) => el("label", className, attrs),
15
- button: (className = "", attrs = {}) => el("button", className, attrs),
16
- };