typography-controller 1.0.3 → 1.2.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/README.md CHANGED
@@ -15,6 +15,7 @@ Adjust font size, letter spacing, word spacing, line height, contrast, and font
15
15
  - Font Family: Choose from predefined fonts (Arial, Georgia, Courier New, Verdana).
16
16
  - Framework‑agnostic — Works in HTML, React, Vue, Svelte, etc.
17
17
  - Public API — getValues(), setValues(), setFeatures(), and change events.
18
+ - EAA - This component is designed with full EAA‑aligned accessibility practices, including keyboard navigation, ARIA labeling, and perceivable focus states.
18
19
 
19
20
  ## 📦 Installation
20
21
 
@@ -1,171 +1,24 @@
1
- import { createComponent as o } from "@lit/react";
2
- import a from "react";
3
- let n = class extends HTMLElement {
1
+ import { createComponent as a } from "@lit/react";
2
+ import s from "react";
3
+ let r = class extends HTMLElement {
4
4
  constructor() {
5
- super(), this.attachShadow({ mode: "open" }), this.shadowRoot.innerHTML = `
6
- <style>
7
- :host {
8
- display: block;
9
- font-family: system-ui, sans-serif;
10
- max-width: 420px;
11
- padding: 18px 20px;
12
- border-radius: 18px;
13
- background: rgba(255, 255, 255, 0.65);
14
- border: 1px solid rgba(0, 0, 0, 0.08);
15
- backdrop-filter: blur(14px);
16
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
17
- color: #0f172a;
18
- }
19
-
20
- .header {
21
- display: flex;
22
- justify-content: space-between;
23
- align-items: baseline;
24
- margin-bottom: 14px;
25
- }
26
-
27
- .title {
28
- font-size: 14px;
29
- font-weight: 600;
30
- letter-spacing: 0.08em;
31
- text-transform: uppercase;
32
- color: #475569;
33
- }
34
-
35
- .badge {
36
- font-size: 11px;
37
- padding: 3px 8px;
38
- border-radius: 999px;
39
- background: rgba(59, 130, 246, 0.12);
40
- color: #1d4ed8;
41
- border: 1px solid rgba(59, 130, 246, 0.35);
42
- }
43
-
44
- .group {
45
- margin-bottom: 14px;
46
- }
47
-
48
- .label-row {
49
- display: flex;
50
- justify-content: space-between;
51
- align-items: center;
52
- margin-bottom: 6px;
53
- }
54
-
55
- label {
56
- font-size: 12px;
57
- font-weight: 600;
58
- color: #0f172a;
59
- }
60
-
61
- .value {
62
- font-size: 11px;
63
- color: #64748b;
64
- }
65
-
66
- input[type="range"] {
67
- -webkit-appearance: none;
68
- width: 100%;
69
- height: 6px;
70
- border-radius: 999px;
71
- background: linear-gradient(90deg, #3b82f6, #60a5fa);
72
- outline: none;
73
- }
74
-
75
- input[type="range"]::-webkit-slider-thumb {
76
- -webkit-appearance: none;
77
- height: 18px;
78
- width: 18px;
79
- border-radius: 50%;
80
- background: white;
81
- border: 1px solid rgba(0, 0, 0, 0.15);
82
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
83
- cursor: pointer;
84
- }
85
-
86
- select {
87
- width: 100%;
88
- padding: 6px 8px;
89
- border-radius: 8px;
90
- border: 1px solid rgba(148, 163, 184, 0.7);
91
- background: rgba(255, 255, 255, 0.9);
92
- font-size: 12px;
93
- color: #0f172a;
94
- outline: none;
95
- }
96
-
97
- select:focus {
98
- border-color: #2563eb;
99
- box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.4);
100
- }
101
- </style>
102
-
103
- <div class="header">
104
- <div class="title">Typography Controls</div>
105
- <div class="badge">Web Component</div>
106
- </div>
107
-
108
- <div class="group" id="groupFontSize">
109
- <div class="label-row">
110
- <label for="fontSize">Font size</label>
111
- <span class="value" id="fontSizeValue"></span>
112
- </div>
113
- <input type="range" id="fontSize" min="12" max="60" value="24">
114
- </div>
115
-
116
- <div class="group" id="groupLetterSpacing">
117
- <div class="label-row">
118
- <label for="letterSpacing">Letter spacing</label>
119
- <span class="value" id="letterSpacingValue"></span>
120
- </div>
121
- <input type="range" id="letterSpacing" min="0" max="20" value="0">
122
- </div>
123
-
124
- <div class="group" id="groupWordSpacing">
125
- <div class="label-row">
126
- <label for="wordSpacing">Word spacing</label>
127
- <span class="value" id="wordSpacingValue"></span>
128
- </div>
129
- <input type="range" id="wordSpacing" min="0" max="40" value="0">
130
- </div>
131
-
132
- <div class="group" id="groupLineHeight">
133
- <div class="label-row">
134
- <label for="lineHeight">Line height</label>
135
- <span class="value" id="lineHeightValue"></span>
136
- </div>
137
- <input type="range" id="lineHeight" min="1" max="3" step="0.1" value="1.5">
138
- </div>
139
-
140
- <div class="group" id="groupContrast">
141
- <div class="label-row">
142
- <label for="contrast">Contrast</label>
143
- <span class="value" id="contrastValue"></span>
144
- </div>
145
- <input type="range" id="contrast" min="50" max="200" value="100">
146
- </div>
147
-
148
- <div class="group" id="groupFontFamily">
149
- <div class="label-row">
150
- <label for="fontFamily">Font family</label>
151
- </div>
152
- <select id="fontFamily">
153
- <option value="system-ui, sans-serif">System</option>
154
- <option value="Georgia, serif">Serif</option>
155
- <option value="'Courier New', monospace">Monospace</option>
156
- <option value="Verdana, sans-serif">Verdana</option>
157
- </select>
158
- </div>
159
- `;
5
+ super(), this.attachShadow({ mode: "open" });
160
6
  }
161
- connectedCallback() {
162
- this.targetSelector = this.getAttribute("target"), this.toggleGroup("#groupLetterSpacing", !this.hasAttribute("hide-letter-spacing")), this.toggleGroup("#groupWordSpacing", !this.hasAttribute("hide-word-spacing")), this.toggleGroup("#groupLineHeight", !this.hasAttribute("hide-line-height")), this.toggleGroup("#groupContrast", !this.hasAttribute("hide-contrast")), this.fontSize = this.shadowRoot.querySelector("#fontSize"), this.letterSpacing = this.shadowRoot.querySelector("#letterSpacing"), this.wordSpacing = this.shadowRoot.querySelector("#wordSpacing"), this.lineHeight = this.shadowRoot.querySelector("#lineHeight"), this.contrast = this.shadowRoot.querySelector("#contrast"), this.fontFamily = this.shadowRoot.querySelector("#fontFamily");
163
- const t = this.targetElement;
164
- if (t) {
165
- const i = getComputedStyle(t).fontFamily;
166
- this.fontFamily.value = i;
7
+ async connectedCallback() {
8
+ const [t, i] = await Promise.all([
9
+ fetch("../src//typography-controller.html").then((o) => o.text()),
10
+ fetch("../src/typography-controller.css").then((o) => o.text())
11
+ ]);
12
+ this.shadowRoot.innerHTML = `
13
+ <style>${i}</style>
14
+ ${t}
15
+ `, this.targetSelector = this.getAttribute("target"), this.toggleGroup("#groupLetterSpacing", !this.hasAttribute("hide-letter-spacing")), this.toggleGroup("#groupWordSpacing", !this.hasAttribute("hide-word-spacing")), this.toggleGroup("#groupLineHeight", !this.hasAttribute("hide-line-height")), this.toggleGroup("#groupContrast", !this.hasAttribute("hide-contrast")), this.toggleGroup("#groupFamily", !this.hasAttribute("hide-font-family")), this.fontSize = this.shadowRoot.querySelector("#fontSize"), this.letterSpacing = this.shadowRoot.querySelector("#letterSpacing"), this.wordSpacing = this.shadowRoot.querySelector("#wordSpacing"), this.lineHeight = this.shadowRoot.querySelector("#lineHeight"), this.contrast = this.shadowRoot.querySelector("#contrast"), this.fontFamily = this.shadowRoot.querySelector("#fontFamily");
16
+ const n = this.targetElement;
17
+ if (n) {
18
+ const o = getComputedStyle(n).fontFamily;
19
+ this.fontFamily.value = o;
167
20
  }
168
- this.fontSizeValue = this.shadowRoot.querySelector("#fontSizeValue"), this.letterSpacingValue = this.shadowRoot.querySelector("#letterSpacingValue"), this.wordSpacingValue = this.shadowRoot.querySelector("#wordSpacingValue"), this.lineHeightValue = this.shadowRoot.querySelector("#lineHeightValue"), this.contrastValue = this.shadowRoot.querySelector("#contrastValue");
21
+ this.fontSizeValue = this.shadowRoot.querySelector("#fontSizeValue"), this.letterSpacingValue = this.shadowRoot.querySelector("#letterSpacingValue"), this.wordSpacingValue = this.shadowRoot.querySelector("#wordSpacingValue"), this.lineHeightValue = this.shadowRoot.querySelector("#lineHeightValue"), this.contrastValue = this.shadowRoot.querySelector("#contrastValue"), this.applyRange(this.fontSize, "font-size-min", "font-size-max"), this.applyRange(this.letterSpacing, "letter-spacing-min", "letter-spacing-max"), this.applyRange(this.wordSpacing, "word-spacing-min", "word-spacing-max"), this.applyRange(this.lineHeight, "line-height-min", "line-height-max"), this.applyRange(this.contrast, "contrast-min", "contrast-max");
169
22
  const e = () => {
170
23
  this.update(), this.dispatchEvent(
171
24
  new CustomEvent("change", {
@@ -175,11 +28,11 @@ let n = class extends HTMLElement {
175
28
  })
176
29
  );
177
30
  };
178
- this.fontSize.addEventListener("input", e), this.letterSpacing.addEventListener("input", e), this.wordSpacing.addEventListener("input", e), this.lineHeight.addEventListener("input", e), this.contrast.addEventListener("input", e), this.fontFamily.addEventListener("change", e), this.update();
31
+ this.fontSize.addEventListener("input", e), this.letterSpacing.addEventListener("input", e), this.wordSpacing.addEventListener("input", e), this.lineHeight.addEventListener("input", e), this.contrast.addEventListener("input", e), this.fontFamily.addEventListener("change", e), this.bindSlider(this.fontSize, this.fontSizeValue, "Font size"), this.bindSlider(this.letterSpacing, this.letterSpacingValue, "Letter spacing"), this.bindSlider(this.wordSpacing, this.wordSpacingValue, "Word spacing"), this.bindSlider(this.lineHeight, this.lineHeightValue, "Line height"), this.bindSlider(this.contrast, this.contrastValue, "Contrast"), this.update();
179
32
  }
180
- toggleGroup(t, e) {
181
- const i = this.shadowRoot.querySelector(t);
182
- i && (i.style.display = e ? "block" : "none");
33
+ toggleGroup(t, i) {
34
+ const n = this.shadowRoot.querySelector(t);
35
+ n && (n.style.display = i ? "block" : "none");
183
36
  }
184
37
  get targetElement() {
185
38
  return document.querySelector(this.targetSelector);
@@ -199,15 +52,39 @@ let n = class extends HTMLElement {
199
52
  };
200
53
  }
201
54
  setValues(t = {}) {
202
- t.fontSize !== void 0 && (this.fontSize.value = t.fontSize), t.letterSpacing !== void 0 && (this.letterSpacing.value = t.letterSpacing), t.wordSpacing !== void 0 && (this.wordSpacing.value = t.wordSpacing), t.lineHeight !== void 0 && (this.lineHeight.value = t.lineHeight), t.contrast !== void 0 && (this.contrast.value = t.contrast), t.fontFamily !== void 0 && (this.fontFamily.value = t.fontFamily), this.update();
55
+ if (t.fontSize !== void 0 && (this.fontSize.value = t.fontSize), t.letterSpacing !== void 0 && (this.letterSpacing.value = t.letterSpacing), t.wordSpacing !== void 0 && (this.wordSpacing.value = t.wordSpacing), t.lineHeight !== void 0 && (this.lineHeight.value = t.lineHeight), t.contrast !== void 0 && (this.contrast.value = t.contrast), t.fontFamily !== void 0) {
56
+ const i = this.targetElement;
57
+ if (t.fontFamily === "inherit" && i) {
58
+ const n = getComputedStyle(i).fontFamily;
59
+ this.fontFamily.value = n;
60
+ } else
61
+ this.fontFamily.value = t.fontFamily;
62
+ }
63
+ this.update();
203
64
  }
204
65
  setFeatures(t = {}) {
205
- t.letterSpacing !== void 0 && (this.toggleGroup("#groupLetterSpacing", t.letterSpacing), t.letterSpacing ? this.removeAttribute("hide-letter-spacing") : this.setAttribute("hide-letter-spacing", "")), t.wordSpacing !== void 0 && (this.toggleGroup("#groupWordSpacing", t.wordSpacing), t.wordSpacing ? this.removeAttribute("hide-word-spacing") : this.setAttribute("hide-word-spacing", "")), t.lineHeight !== void 0 && (this.toggleGroup("#groupLineHeight", t.lineHeight), t.lineHeight ? this.removeAttribute("hide-line-height") : this.setAttribute("hide-line-height", "")), t.contrast !== void 0 && (this.toggleGroup("#groupContrast", t.contrast), t.contrast ? this.removeAttribute("hide-contrast") : this.setAttribute("hide-contrast", ""));
66
+ t.letterSpacing !== void 0 && (this.toggleGroup("#groupLetterSpacing", t.letterSpacing), t.letterSpacing ? this.removeAttribute("hide-letter-spacing") : this.setAttribute("hide-letter-spacing", "")), t.wordSpacing !== void 0 && (this.toggleGroup("#groupWordSpacing", t.wordSpacing), t.wordSpacing ? this.removeAttribute("hide-word-spacing") : this.setAttribute("hide-word-spacing", "")), t.lineHeight !== void 0 && (this.toggleGroup("#groupLineHeight", t.lineHeight), t.lineHeight ? this.removeAttribute("hide-line-height") : this.setAttribute("hide-line-height", "")), t.contrast !== void 0 && (this.toggleGroup("#groupContrast", t.contrast), t.contrast ? this.removeAttribute("hide-contrast") : this.setAttribute("hide-contrast", "")), t.fontFamily !== void 0 && (this.toggleGroup("#groupFontFamily", t.fontFamily), t.fontFamily ? this.removeAttribute("hide-font-family") : this.setAttribute("hide-font-family", ""));
67
+ }
68
+ bindSlider(t, i, n) {
69
+ i.textContent = t.value, t.addEventListener("input", () => {
70
+ const e = t.value;
71
+ i.textContent = e, t.setAttribute("aria-valuenow", e), this.update(), this.dispatchEvent(
72
+ new CustomEvent("change", {
73
+ detail: this.getValues(),
74
+ bubbles: !0,
75
+ composed: !0
76
+ })
77
+ );
78
+ });
79
+ }
80
+ applyRange(t, i, n) {
81
+ const e = this.getAttribute(i), o = this.getAttribute(n);
82
+ e !== null && (t.min = e, t.setAttribute("aria-valuemin", e)), o !== null && (t.max = o, t.setAttribute("aria-valuemax", o));
206
83
  }
207
84
  };
208
- customElements.define("typography-controller", n);
209
- const d = o({
210
- react: a,
85
+ customElements.define("typography-controller", r);
86
+ const p = a({
87
+ react: s,
211
88
  tagName: "typography-controller",
212
89
  elementClass: customElements.get("typography-controller"),
213
90
  events: {
@@ -215,5 +92,5 @@ const d = o({
215
92
  }
216
93
  });
217
94
  export {
218
- d as TypographyController
95
+ p as TypographyController
219
96
  };
@@ -1,169 +1,22 @@
1
- class o extends HTMLElement {
1
+ class a extends HTMLElement {
2
2
  constructor() {
3
- super(), this.attachShadow({ mode: "open" }), this.shadowRoot.innerHTML = `
4
- <style>
5
- :host {
6
- display: block;
7
- font-family: system-ui, sans-serif;
8
- max-width: 420px;
9
- padding: 18px 20px;
10
- border-radius: 18px;
11
- background: rgba(255, 255, 255, 0.65);
12
- border: 1px solid rgba(0, 0, 0, 0.08);
13
- backdrop-filter: blur(14px);
14
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
15
- color: #0f172a;
16
- }
17
-
18
- .header {
19
- display: flex;
20
- justify-content: space-between;
21
- align-items: baseline;
22
- margin-bottom: 14px;
23
- }
24
-
25
- .title {
26
- font-size: 14px;
27
- font-weight: 600;
28
- letter-spacing: 0.08em;
29
- text-transform: uppercase;
30
- color: #475569;
31
- }
32
-
33
- .badge {
34
- font-size: 11px;
35
- padding: 3px 8px;
36
- border-radius: 999px;
37
- background: rgba(59, 130, 246, 0.12);
38
- color: #1d4ed8;
39
- border: 1px solid rgba(59, 130, 246, 0.35);
40
- }
41
-
42
- .group {
43
- margin-bottom: 14px;
44
- }
45
-
46
- .label-row {
47
- display: flex;
48
- justify-content: space-between;
49
- align-items: center;
50
- margin-bottom: 6px;
51
- }
52
-
53
- label {
54
- font-size: 12px;
55
- font-weight: 600;
56
- color: #0f172a;
57
- }
58
-
59
- .value {
60
- font-size: 11px;
61
- color: #64748b;
62
- }
63
-
64
- input[type="range"] {
65
- -webkit-appearance: none;
66
- width: 100%;
67
- height: 6px;
68
- border-radius: 999px;
69
- background: linear-gradient(90deg, #3b82f6, #60a5fa);
70
- outline: none;
71
- }
72
-
73
- input[type="range"]::-webkit-slider-thumb {
74
- -webkit-appearance: none;
75
- height: 18px;
76
- width: 18px;
77
- border-radius: 50%;
78
- background: white;
79
- border: 1px solid rgba(0, 0, 0, 0.15);
80
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
81
- cursor: pointer;
82
- }
83
-
84
- select {
85
- width: 100%;
86
- padding: 6px 8px;
87
- border-radius: 8px;
88
- border: 1px solid rgba(148, 163, 184, 0.7);
89
- background: rgba(255, 255, 255, 0.9);
90
- font-size: 12px;
91
- color: #0f172a;
92
- outline: none;
93
- }
94
-
95
- select:focus {
96
- border-color: #2563eb;
97
- box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.4);
98
- }
99
- </style>
100
-
101
- <div class="header">
102
- <div class="title">Typography Controls</div>
103
- <div class="badge">Web Component</div>
104
- </div>
105
-
106
- <div class="group" id="groupFontSize">
107
- <div class="label-row">
108
- <label for="fontSize">Font size</label>
109
- <span class="value" id="fontSizeValue"></span>
110
- </div>
111
- <input type="range" id="fontSize" min="12" max="60" value="24">
112
- </div>
113
-
114
- <div class="group" id="groupLetterSpacing">
115
- <div class="label-row">
116
- <label for="letterSpacing">Letter spacing</label>
117
- <span class="value" id="letterSpacingValue"></span>
118
- </div>
119
- <input type="range" id="letterSpacing" min="0" max="20" value="0">
120
- </div>
121
-
122
- <div class="group" id="groupWordSpacing">
123
- <div class="label-row">
124
- <label for="wordSpacing">Word spacing</label>
125
- <span class="value" id="wordSpacingValue"></span>
126
- </div>
127
- <input type="range" id="wordSpacing" min="0" max="40" value="0">
128
- </div>
129
-
130
- <div class="group" id="groupLineHeight">
131
- <div class="label-row">
132
- <label for="lineHeight">Line height</label>
133
- <span class="value" id="lineHeightValue"></span>
134
- </div>
135
- <input type="range" id="lineHeight" min="1" max="3" step="0.1" value="1.5">
136
- </div>
137
-
138
- <div class="group" id="groupContrast">
139
- <div class="label-row">
140
- <label for="contrast">Contrast</label>
141
- <span class="value" id="contrastValue"></span>
142
- </div>
143
- <input type="range" id="contrast" min="50" max="200" value="100">
144
- </div>
145
-
146
- <div class="group" id="groupFontFamily">
147
- <div class="label-row">
148
- <label for="fontFamily">Font family</label>
149
- </div>
150
- <select id="fontFamily">
151
- <option value="system-ui, sans-serif">System</option>
152
- <option value="Georgia, serif">Serif</option>
153
- <option value="'Courier New', monospace">Monospace</option>
154
- <option value="Verdana, sans-serif">Verdana</option>
155
- </select>
156
- </div>
157
- `;
3
+ super(), this.attachShadow({ mode: "open" });
158
4
  }
159
- connectedCallback() {
160
- this.targetSelector = this.getAttribute("target"), this.toggleGroup("#groupLetterSpacing", !this.hasAttribute("hide-letter-spacing")), this.toggleGroup("#groupWordSpacing", !this.hasAttribute("hide-word-spacing")), this.toggleGroup("#groupLineHeight", !this.hasAttribute("hide-line-height")), this.toggleGroup("#groupContrast", !this.hasAttribute("hide-contrast")), this.fontSize = this.shadowRoot.querySelector("#fontSize"), this.letterSpacing = this.shadowRoot.querySelector("#letterSpacing"), this.wordSpacing = this.shadowRoot.querySelector("#wordSpacing"), this.lineHeight = this.shadowRoot.querySelector("#lineHeight"), this.contrast = this.shadowRoot.querySelector("#contrast"), this.fontFamily = this.shadowRoot.querySelector("#fontFamily");
161
- const t = this.targetElement;
162
- if (t) {
163
- const i = getComputedStyle(t).fontFamily;
164
- this.fontFamily.value = i;
5
+ async connectedCallback() {
6
+ const [t, i] = await Promise.all([
7
+ fetch("../src//typography-controller.html").then((o) => o.text()),
8
+ fetch("../src/typography-controller.css").then((o) => o.text())
9
+ ]);
10
+ this.shadowRoot.innerHTML = `
11
+ <style>${i}</style>
12
+ ${t}
13
+ `, this.targetSelector = this.getAttribute("target"), this.toggleGroup("#groupLetterSpacing", !this.hasAttribute("hide-letter-spacing")), this.toggleGroup("#groupWordSpacing", !this.hasAttribute("hide-word-spacing")), this.toggleGroup("#groupLineHeight", !this.hasAttribute("hide-line-height")), this.toggleGroup("#groupContrast", !this.hasAttribute("hide-contrast")), this.toggleGroup("#groupFamily", !this.hasAttribute("hide-font-family")), this.fontSize = this.shadowRoot.querySelector("#fontSize"), this.letterSpacing = this.shadowRoot.querySelector("#letterSpacing"), this.wordSpacing = this.shadowRoot.querySelector("#wordSpacing"), this.lineHeight = this.shadowRoot.querySelector("#lineHeight"), this.contrast = this.shadowRoot.querySelector("#contrast"), this.fontFamily = this.shadowRoot.querySelector("#fontFamily");
14
+ const n = this.targetElement;
15
+ if (n) {
16
+ const o = getComputedStyle(n).fontFamily;
17
+ this.fontFamily.value = o;
165
18
  }
166
- this.fontSizeValue = this.shadowRoot.querySelector("#fontSizeValue"), this.letterSpacingValue = this.shadowRoot.querySelector("#letterSpacingValue"), this.wordSpacingValue = this.shadowRoot.querySelector("#wordSpacingValue"), this.lineHeightValue = this.shadowRoot.querySelector("#lineHeightValue"), this.contrastValue = this.shadowRoot.querySelector("#contrastValue");
19
+ this.fontSizeValue = this.shadowRoot.querySelector("#fontSizeValue"), this.letterSpacingValue = this.shadowRoot.querySelector("#letterSpacingValue"), this.wordSpacingValue = this.shadowRoot.querySelector("#wordSpacingValue"), this.lineHeightValue = this.shadowRoot.querySelector("#lineHeightValue"), this.contrastValue = this.shadowRoot.querySelector("#contrastValue"), this.applyRange(this.fontSize, "font-size-min", "font-size-max"), this.applyRange(this.letterSpacing, "letter-spacing-min", "letter-spacing-max"), this.applyRange(this.wordSpacing, "word-spacing-min", "word-spacing-max"), this.applyRange(this.lineHeight, "line-height-min", "line-height-max"), this.applyRange(this.contrast, "contrast-min", "contrast-max");
167
20
  const e = () => {
168
21
  this.update(), this.dispatchEvent(
169
22
  new CustomEvent("change", {
@@ -173,11 +26,11 @@ class o extends HTMLElement {
173
26
  })
174
27
  );
175
28
  };
176
- this.fontSize.addEventListener("input", e), this.letterSpacing.addEventListener("input", e), this.wordSpacing.addEventListener("input", e), this.lineHeight.addEventListener("input", e), this.contrast.addEventListener("input", e), this.fontFamily.addEventListener("change", e), this.update();
29
+ this.fontSize.addEventListener("input", e), this.letterSpacing.addEventListener("input", e), this.wordSpacing.addEventListener("input", e), this.lineHeight.addEventListener("input", e), this.contrast.addEventListener("input", e), this.fontFamily.addEventListener("change", e), this.bindSlider(this.fontSize, this.fontSizeValue, "Font size"), this.bindSlider(this.letterSpacing, this.letterSpacingValue, "Letter spacing"), this.bindSlider(this.wordSpacing, this.wordSpacingValue, "Word spacing"), this.bindSlider(this.lineHeight, this.lineHeightValue, "Line height"), this.bindSlider(this.contrast, this.contrastValue, "Contrast"), this.update();
177
30
  }
178
- toggleGroup(t, e) {
179
- const i = this.shadowRoot.querySelector(t);
180
- i && (i.style.display = e ? "block" : "none");
31
+ toggleGroup(t, i) {
32
+ const n = this.shadowRoot.querySelector(t);
33
+ n && (n.style.display = i ? "block" : "none");
181
34
  }
182
35
  get targetElement() {
183
36
  return document.querySelector(this.targetSelector);
@@ -197,10 +50,34 @@ class o extends HTMLElement {
197
50
  };
198
51
  }
199
52
  setValues(t = {}) {
200
- t.fontSize !== void 0 && (this.fontSize.value = t.fontSize), t.letterSpacing !== void 0 && (this.letterSpacing.value = t.letterSpacing), t.wordSpacing !== void 0 && (this.wordSpacing.value = t.wordSpacing), t.lineHeight !== void 0 && (this.lineHeight.value = t.lineHeight), t.contrast !== void 0 && (this.contrast.value = t.contrast), t.fontFamily !== void 0 && (this.fontFamily.value = t.fontFamily), this.update();
53
+ if (t.fontSize !== void 0 && (this.fontSize.value = t.fontSize), t.letterSpacing !== void 0 && (this.letterSpacing.value = t.letterSpacing), t.wordSpacing !== void 0 && (this.wordSpacing.value = t.wordSpacing), t.lineHeight !== void 0 && (this.lineHeight.value = t.lineHeight), t.contrast !== void 0 && (this.contrast.value = t.contrast), t.fontFamily !== void 0) {
54
+ const i = this.targetElement;
55
+ if (t.fontFamily === "inherit" && i) {
56
+ const n = getComputedStyle(i).fontFamily;
57
+ this.fontFamily.value = n;
58
+ } else
59
+ this.fontFamily.value = t.fontFamily;
60
+ }
61
+ this.update();
201
62
  }
202
63
  setFeatures(t = {}) {
203
- t.letterSpacing !== void 0 && (this.toggleGroup("#groupLetterSpacing", t.letterSpacing), t.letterSpacing ? this.removeAttribute("hide-letter-spacing") : this.setAttribute("hide-letter-spacing", "")), t.wordSpacing !== void 0 && (this.toggleGroup("#groupWordSpacing", t.wordSpacing), t.wordSpacing ? this.removeAttribute("hide-word-spacing") : this.setAttribute("hide-word-spacing", "")), t.lineHeight !== void 0 && (this.toggleGroup("#groupLineHeight", t.lineHeight), t.lineHeight ? this.removeAttribute("hide-line-height") : this.setAttribute("hide-line-height", "")), t.contrast !== void 0 && (this.toggleGroup("#groupContrast", t.contrast), t.contrast ? this.removeAttribute("hide-contrast") : this.setAttribute("hide-contrast", ""));
64
+ t.letterSpacing !== void 0 && (this.toggleGroup("#groupLetterSpacing", t.letterSpacing), t.letterSpacing ? this.removeAttribute("hide-letter-spacing") : this.setAttribute("hide-letter-spacing", "")), t.wordSpacing !== void 0 && (this.toggleGroup("#groupWordSpacing", t.wordSpacing), t.wordSpacing ? this.removeAttribute("hide-word-spacing") : this.setAttribute("hide-word-spacing", "")), t.lineHeight !== void 0 && (this.toggleGroup("#groupLineHeight", t.lineHeight), t.lineHeight ? this.removeAttribute("hide-line-height") : this.setAttribute("hide-line-height", "")), t.contrast !== void 0 && (this.toggleGroup("#groupContrast", t.contrast), t.contrast ? this.removeAttribute("hide-contrast") : this.setAttribute("hide-contrast", "")), t.fontFamily !== void 0 && (this.toggleGroup("#groupFontFamily", t.fontFamily), t.fontFamily ? this.removeAttribute("hide-font-family") : this.setAttribute("hide-font-family", ""));
65
+ }
66
+ bindSlider(t, i, n) {
67
+ i.textContent = t.value, t.addEventListener("input", () => {
68
+ const e = t.value;
69
+ i.textContent = e, t.setAttribute("aria-valuenow", e), this.update(), this.dispatchEvent(
70
+ new CustomEvent("change", {
71
+ detail: this.getValues(),
72
+ bubbles: !0,
73
+ composed: !0
74
+ })
75
+ );
76
+ });
77
+ }
78
+ applyRange(t, i, n) {
79
+ const e = this.getAttribute(i), o = this.getAttribute(n);
80
+ e !== null && (t.min = e, t.setAttribute("aria-valuemin", e)), o !== null && (t.max = o, t.setAttribute("aria-valuemax", o));
204
81
  }
205
82
  }
206
- customElements.define("typography-controller", o);
83
+ customElements.define("typography-controller", a);
@@ -1,155 +1,4 @@
1
- (function(i){typeof define=="function"&&define.amd?define(i):i()})(function(){"use strict";class i extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.shadowRoot.innerHTML=`
2
- <style>
3
- :host {
4
- display: block;
5
- font-family: system-ui, sans-serif;
6
- max-width: 420px;
7
- padding: 18px 20px;
8
- border-radius: 18px;
9
- background: rgba(255, 255, 255, 0.65);
10
- border: 1px solid rgba(0, 0, 0, 0.08);
11
- backdrop-filter: blur(14px);
12
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
13
- color: #0f172a;
14
- }
15
-
16
- .header {
17
- display: flex;
18
- justify-content: space-between;
19
- align-items: baseline;
20
- margin-bottom: 14px;
21
- }
22
-
23
- .title {
24
- font-size: 14px;
25
- font-weight: 600;
26
- letter-spacing: 0.08em;
27
- text-transform: uppercase;
28
- color: #475569;
29
- }
30
-
31
- .badge {
32
- font-size: 11px;
33
- padding: 3px 8px;
34
- border-radius: 999px;
35
- background: rgba(59, 130, 246, 0.12);
36
- color: #1d4ed8;
37
- border: 1px solid rgba(59, 130, 246, 0.35);
38
- }
39
-
40
- .group {
41
- margin-bottom: 14px;
42
- }
43
-
44
- .label-row {
45
- display: flex;
46
- justify-content: space-between;
47
- align-items: center;
48
- margin-bottom: 6px;
49
- }
50
-
51
- label {
52
- font-size: 12px;
53
- font-weight: 600;
54
- color: #0f172a;
55
- }
56
-
57
- .value {
58
- font-size: 11px;
59
- color: #64748b;
60
- }
61
-
62
- input[type="range"] {
63
- -webkit-appearance: none;
64
- width: 100%;
65
- height: 6px;
66
- border-radius: 999px;
67
- background: linear-gradient(90deg, #3b82f6, #60a5fa);
68
- outline: none;
69
- }
70
-
71
- input[type="range"]::-webkit-slider-thumb {
72
- -webkit-appearance: none;
73
- height: 18px;
74
- width: 18px;
75
- border-radius: 50%;
76
- background: white;
77
- border: 1px solid rgba(0, 0, 0, 0.15);
78
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
79
- cursor: pointer;
80
- }
81
-
82
- select {
83
- width: 100%;
84
- padding: 6px 8px;
85
- border-radius: 8px;
86
- border: 1px solid rgba(148, 163, 184, 0.7);
87
- background: rgba(255, 255, 255, 0.9);
88
- font-size: 12px;
89
- color: #0f172a;
90
- outline: none;
91
- }
92
-
93
- select:focus {
94
- border-color: #2563eb;
95
- box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.4);
96
- }
97
- </style>
98
-
99
- <div class="header">
100
- <div class="title">Typography Controls</div>
101
- <div class="badge">Web Component</div>
102
- </div>
103
-
104
- <div class="group" id="groupFontSize">
105
- <div class="label-row">
106
- <label for="fontSize">Font size</label>
107
- <span class="value" id="fontSizeValue"></span>
108
- </div>
109
- <input type="range" id="fontSize" min="12" max="60" value="24">
110
- </div>
111
-
112
- <div class="group" id="groupLetterSpacing">
113
- <div class="label-row">
114
- <label for="letterSpacing">Letter spacing</label>
115
- <span class="value" id="letterSpacingValue"></span>
116
- </div>
117
- <input type="range" id="letterSpacing" min="0" max="20" value="0">
118
- </div>
119
-
120
- <div class="group" id="groupWordSpacing">
121
- <div class="label-row">
122
- <label for="wordSpacing">Word spacing</label>
123
- <span class="value" id="wordSpacingValue"></span>
124
- </div>
125
- <input type="range" id="wordSpacing" min="0" max="40" value="0">
126
- </div>
127
-
128
- <div class="group" id="groupLineHeight">
129
- <div class="label-row">
130
- <label for="lineHeight">Line height</label>
131
- <span class="value" id="lineHeightValue"></span>
132
- </div>
133
- <input type="range" id="lineHeight" min="1" max="3" step="0.1" value="1.5">
134
- </div>
135
-
136
- <div class="group" id="groupContrast">
137
- <div class="label-row">
138
- <label for="contrast">Contrast</label>
139
- <span class="value" id="contrastValue"></span>
140
- </div>
141
- <input type="range" id="contrast" min="50" max="200" value="100">
142
- </div>
143
-
144
- <div class="group" id="groupFontFamily">
145
- <div class="label-row">
146
- <label for="fontFamily">Font family</label>
147
- </div>
148
- <select id="fontFamily">
149
- <option value="system-ui, sans-serif">System</option>
150
- <option value="Georgia, serif">Serif</option>
151
- <option value="'Courier New', monospace">Monospace</option>
152
- <option value="Verdana, sans-serif">Verdana</option>
153
- </select>
154
- </div>
155
- `}connectedCallback(){this.targetSelector=this.getAttribute("target"),this.toggleGroup("#groupLetterSpacing",!this.hasAttribute("hide-letter-spacing")),this.toggleGroup("#groupWordSpacing",!this.hasAttribute("hide-word-spacing")),this.toggleGroup("#groupLineHeight",!this.hasAttribute("hide-line-height")),this.toggleGroup("#groupContrast",!this.hasAttribute("hide-contrast")),this.fontSize=this.shadowRoot.querySelector("#fontSize"),this.letterSpacing=this.shadowRoot.querySelector("#letterSpacing"),this.wordSpacing=this.shadowRoot.querySelector("#wordSpacing"),this.lineHeight=this.shadowRoot.querySelector("#lineHeight"),this.contrast=this.shadowRoot.querySelector("#contrast"),this.fontFamily=this.shadowRoot.querySelector("#fontFamily");const t=this.targetElement;if(t){const o=getComputedStyle(t).fontFamily;this.fontFamily.value=o}this.fontSizeValue=this.shadowRoot.querySelector("#fontSizeValue"),this.letterSpacingValue=this.shadowRoot.querySelector("#letterSpacingValue"),this.wordSpacingValue=this.shadowRoot.querySelector("#wordSpacingValue"),this.lineHeightValue=this.shadowRoot.querySelector("#lineHeightValue"),this.contrastValue=this.shadowRoot.querySelector("#contrastValue");const e=()=>{this.update(),this.dispatchEvent(new CustomEvent("change",{detail:this.getValues(),bubbles:!0,composed:!0}))};this.fontSize.addEventListener("input",e),this.letterSpacing.addEventListener("input",e),this.wordSpacing.addEventListener("input",e),this.lineHeight.addEventListener("input",e),this.contrast.addEventListener("input",e),this.fontFamily.addEventListener("change",e),this.update()}toggleGroup(t,e){const o=this.shadowRoot.querySelector(t);o&&(o.style.display=e?"block":"none")}get targetElement(){return document.querySelector(this.targetSelector)}update(){const t=this.targetElement;t&&(t.style.fontSize=`${this.fontSize.value}px`,t.style.letterSpacing=`${this.letterSpacing.value}px`,t.style.wordSpacing=`${this.wordSpacing.value}px`,t.style.lineHeight=this.lineHeight.value,t.style.filter=`contrast(${this.contrast.value}%)`,t.style.fontFamily=this.fontFamily.value,this.fontSizeValue.textContent=`${this.fontSize.value}px`,this.letterSpacingValue.textContent=`${this.letterSpacing.value}px`,this.wordSpacingValue.textContent=`${this.wordSpacing.value}px`,this.lineHeightValue.textContent=this.lineHeight.value,this.contrastValue.textContent=`${this.contrast.value}%`)}getValues(){return{fontSize:Number(this.fontSize.value),letterSpacing:Number(this.letterSpacing.value),wordSpacing:Number(this.wordSpacing.value),lineHeight:Number(this.lineHeight.value),contrast:Number(this.contrast.value),fontFamily:this.fontFamily.value}}setValues(t={}){t.fontSize!==void 0&&(this.fontSize.value=t.fontSize),t.letterSpacing!==void 0&&(this.letterSpacing.value=t.letterSpacing),t.wordSpacing!==void 0&&(this.wordSpacing.value=t.wordSpacing),t.lineHeight!==void 0&&(this.lineHeight.value=t.lineHeight),t.contrast!==void 0&&(this.contrast.value=t.contrast),t.fontFamily!==void 0&&(this.fontFamily.value=t.fontFamily),this.update()}setFeatures(t={}){t.letterSpacing!==void 0&&(this.toggleGroup("#groupLetterSpacing",t.letterSpacing),t.letterSpacing?this.removeAttribute("hide-letter-spacing"):this.setAttribute("hide-letter-spacing","")),t.wordSpacing!==void 0&&(this.toggleGroup("#groupWordSpacing",t.wordSpacing),t.wordSpacing?this.removeAttribute("hide-word-spacing"):this.setAttribute("hide-word-spacing","")),t.lineHeight!==void 0&&(this.toggleGroup("#groupLineHeight",t.lineHeight),t.lineHeight?this.removeAttribute("hide-line-height"):this.setAttribute("hide-line-height","")),t.contrast!==void 0&&(this.toggleGroup("#groupContrast",t.contrast),t.contrast?this.removeAttribute("hide-contrast"):this.setAttribute("hide-contrast",""))}}customElements.define("typography-controller",i)});
1
+ (function(a){typeof define=="function"&&define.amd?define(a):a()})((function(){"use strict";class a extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}async connectedCallback(){const[t,i]=await Promise.all([fetch("../src//typography-controller.html").then(o=>o.text()),fetch("../src/typography-controller.css").then(o=>o.text())]);this.shadowRoot.innerHTML=`
2
+ <style>${i}</style>
3
+ ${t}
4
+ `,this.targetSelector=this.getAttribute("target"),this.toggleGroup("#groupLetterSpacing",!this.hasAttribute("hide-letter-spacing")),this.toggleGroup("#groupWordSpacing",!this.hasAttribute("hide-word-spacing")),this.toggleGroup("#groupLineHeight",!this.hasAttribute("hide-line-height")),this.toggleGroup("#groupContrast",!this.hasAttribute("hide-contrast")),this.toggleGroup("#groupFamily",!this.hasAttribute("hide-font-family")),this.fontSize=this.shadowRoot.querySelector("#fontSize"),this.letterSpacing=this.shadowRoot.querySelector("#letterSpacing"),this.wordSpacing=this.shadowRoot.querySelector("#wordSpacing"),this.lineHeight=this.shadowRoot.querySelector("#lineHeight"),this.contrast=this.shadowRoot.querySelector("#contrast"),this.fontFamily=this.shadowRoot.querySelector("#fontFamily");const n=this.targetElement;if(n){const o=getComputedStyle(n).fontFamily;this.fontFamily.value=o}this.fontSizeValue=this.shadowRoot.querySelector("#fontSizeValue"),this.letterSpacingValue=this.shadowRoot.querySelector("#letterSpacingValue"),this.wordSpacingValue=this.shadowRoot.querySelector("#wordSpacingValue"),this.lineHeightValue=this.shadowRoot.querySelector("#lineHeightValue"),this.contrastValue=this.shadowRoot.querySelector("#contrastValue"),this.applyRange(this.fontSize,"font-size-min","font-size-max"),this.applyRange(this.letterSpacing,"letter-spacing-min","letter-spacing-max"),this.applyRange(this.wordSpacing,"word-spacing-min","word-spacing-max"),this.applyRange(this.lineHeight,"line-height-min","line-height-max"),this.applyRange(this.contrast,"contrast-min","contrast-max");const e=()=>{this.update(),this.dispatchEvent(new CustomEvent("change",{detail:this.getValues(),bubbles:!0,composed:!0}))};this.fontSize.addEventListener("input",e),this.letterSpacing.addEventListener("input",e),this.wordSpacing.addEventListener("input",e),this.lineHeight.addEventListener("input",e),this.contrast.addEventListener("input",e),this.fontFamily.addEventListener("change",e),this.bindSlider(this.fontSize,this.fontSizeValue,"Font size"),this.bindSlider(this.letterSpacing,this.letterSpacingValue,"Letter spacing"),this.bindSlider(this.wordSpacing,this.wordSpacingValue,"Word spacing"),this.bindSlider(this.lineHeight,this.lineHeightValue,"Line height"),this.bindSlider(this.contrast,this.contrastValue,"Contrast"),this.update()}toggleGroup(t,i){const n=this.shadowRoot.querySelector(t);n&&(n.style.display=i?"block":"none")}get targetElement(){return document.querySelector(this.targetSelector)}update(){const t=this.targetElement;t&&(t.style.fontSize=`${this.fontSize.value}px`,t.style.letterSpacing=`${this.letterSpacing.value}px`,t.style.wordSpacing=`${this.wordSpacing.value}px`,t.style.lineHeight=this.lineHeight.value,t.style.filter=`contrast(${this.contrast.value}%)`,t.style.fontFamily=this.fontFamily.value,this.fontSizeValue.textContent=`${this.fontSize.value}px`,this.letterSpacingValue.textContent=`${this.letterSpacing.value}px`,this.wordSpacingValue.textContent=`${this.wordSpacing.value}px`,this.lineHeightValue.textContent=this.lineHeight.value,this.contrastValue.textContent=`${this.contrast.value}%`)}getValues(){return{fontSize:Number(this.fontSize.value),letterSpacing:Number(this.letterSpacing.value),wordSpacing:Number(this.wordSpacing.value),lineHeight:Number(this.lineHeight.value),contrast:Number(this.contrast.value),fontFamily:this.fontFamily.value}}setValues(t={}){if(t.fontSize!==void 0&&(this.fontSize.value=t.fontSize),t.letterSpacing!==void 0&&(this.letterSpacing.value=t.letterSpacing),t.wordSpacing!==void 0&&(this.wordSpacing.value=t.wordSpacing),t.lineHeight!==void 0&&(this.lineHeight.value=t.lineHeight),t.contrast!==void 0&&(this.contrast.value=t.contrast),t.fontFamily!==void 0){const i=this.targetElement;if(t.fontFamily==="inherit"&&i){const n=getComputedStyle(i).fontFamily;this.fontFamily.value=n}else this.fontFamily.value=t.fontFamily}this.update()}setFeatures(t={}){t.letterSpacing!==void 0&&(this.toggleGroup("#groupLetterSpacing",t.letterSpacing),t.letterSpacing?this.removeAttribute("hide-letter-spacing"):this.setAttribute("hide-letter-spacing","")),t.wordSpacing!==void 0&&(this.toggleGroup("#groupWordSpacing",t.wordSpacing),t.wordSpacing?this.removeAttribute("hide-word-spacing"):this.setAttribute("hide-word-spacing","")),t.lineHeight!==void 0&&(this.toggleGroup("#groupLineHeight",t.lineHeight),t.lineHeight?this.removeAttribute("hide-line-height"):this.setAttribute("hide-line-height","")),t.contrast!==void 0&&(this.toggleGroup("#groupContrast",t.contrast),t.contrast?this.removeAttribute("hide-contrast"):this.setAttribute("hide-contrast","")),t.fontFamily!==void 0&&(this.toggleGroup("#groupFontFamily",t.fontFamily),t.fontFamily?this.removeAttribute("hide-font-family"):this.setAttribute("hide-font-family",""))}bindSlider(t,i,n){i.textContent=t.value,t.addEventListener("input",()=>{const e=t.value;i.textContent=e,t.setAttribute("aria-valuenow",e),this.update(),this.dispatchEvent(new CustomEvent("change",{detail:this.getValues(),bubbles:!0,composed:!0}))})}applyRange(t,i,n){const e=this.getAttribute(i),o=this.getAttribute(n);e!==null&&(t.min=e,t.setAttribute("aria-valuemin",e)),o!==null&&(t.max=o,t.setAttribute("aria-valuemax",o))}}customElements.define("typography-controller",a)}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typography-controller",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
4
  "description": "A framework-agnostic Web Component that provides a beautiful typography control panel with sliders for font size, letter spacing, word spacing, line height, contrast, and font family.",
5
5
  "author": "Achuth Kamath",
6
6
  "license": "MIT",
@@ -44,14 +44,14 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@semantic-release/changelog": "^6.0.3",
47
- "@semantic-release/commit-analyzer": "^13.0.0",
47
+ "@semantic-release/commit-analyzer": "^13.0.1",
48
48
  "@semantic-release/git": "^10.0.1",
49
49
  "@semantic-release/github": "^12.0.3",
50
50
  "@semantic-release/npm": "^13.1.3",
51
- "@semantic-release/release-notes-generator": "^14.0.1",
51
+ "@semantic-release/release-notes-generator": "^14.1.0",
52
52
  "@vitejs/plugin-react": "^5.1.2",
53
53
  "semantic-release": "^25.0.3",
54
- "vite": "^5.4.21"
54
+ "vite": "^7.3.1"
55
55
  },
56
56
  "dependencies": {
57
57
  "user": "^0.0.0"
@@ -0,0 +1,22 @@
1
+ export class TypographyState {
2
+ constructor(targetElement) {
3
+ this.target = targetElement;
4
+ }
5
+
6
+ resolveFontFamily(value) {
7
+ if (value === "inherit" && this.target) {
8
+ return getComputedStyle(this.target).fontFamily;
9
+ }
10
+ return value;
11
+ }
12
+
13
+ apply(values = {}) {
14
+ const resolved = { ...values };
15
+
16
+ if (values.fontFamily !== undefined) {
17
+ resolved.fontFamily = this.resolveFontFamily(values.fontFamily);
18
+ }
19
+
20
+ return resolved;
21
+ }
22
+ }
@@ -0,0 +1,113 @@
1
+ :host {
2
+ display: block;
3
+ font-family: system-ui, sans-serif;
4
+ max-width: 420px;
5
+ padding: 18px 20px;
6
+ border-radius: 18px;
7
+ background: rgba(255, 255, 255, 0.65);
8
+ border: 1px solid rgba(0, 0, 0, 0.08);
9
+ backdrop-filter: blur(14px);
10
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
11
+ color: #0f172a;
12
+ }
13
+
14
+ .header {
15
+ display: flex;
16
+ justify-content: space-between;
17
+ align-items: baseline;
18
+ margin-bottom: 14px;
19
+ }
20
+
21
+ .title {
22
+ font-size: 14px;
23
+ font-weight: 600;
24
+ letter-spacing: 0.08em;
25
+ text-transform: uppercase;
26
+ color: #475569;
27
+ }
28
+
29
+ .badge {
30
+ font-size: 11px;
31
+ padding: 3px 8px;
32
+ border-radius: 999px;
33
+ background: rgba(59, 130, 246, 0.12);
34
+ color: #1d4ed8;
35
+ border: 1px solid rgba(59, 130, 246, 0.35);
36
+ }
37
+
38
+ .group {
39
+ margin-bottom: 14px;
40
+ }
41
+
42
+ .label-row {
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ margin-bottom: 6px;
47
+ }
48
+
49
+ label {
50
+ font-size: 12px;
51
+ font-weight: 600;
52
+ color: #0f172a;
53
+ }
54
+
55
+ .value {
56
+ font-size: 11px;
57
+ color: #64748b;
58
+ }
59
+
60
+ input[type="range"] {
61
+ -webkit-appearance: none;
62
+ width: 100%;
63
+ height: 6px;
64
+ border-radius: 999px;
65
+ background: linear-gradient(90deg, #3b82f6, #60a5fa);
66
+ outline: none;
67
+ }
68
+
69
+ input[type="range"]::-webkit-slider-thumb {
70
+ -webkit-appearance: none;
71
+ height: 18px;
72
+ width: 18px;
73
+ border-radius: 50%;
74
+ background: white;
75
+ border: 1px solid rgba(0, 0, 0, 0.15);
76
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
77
+ cursor: pointer;
78
+ }
79
+
80
+ select {
81
+ width: 100%;
82
+ padding: 6px 8px;
83
+ border-radius: 8px;
84
+ border: 1px solid rgba(148, 163, 184, 0.7);
85
+ background: rgba(255, 255, 255, 0.9);
86
+ font-size: 12px;
87
+ color: #0f172a;
88
+ outline: none;
89
+ }
90
+
91
+ select:focus {
92
+ outline: none;
93
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35);
94
+ border-radius: 999px;
95
+ }
96
+
97
+ input[type="range"]:focus {
98
+ outline: none;
99
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35);
100
+ border-radius: 999px;
101
+ }
102
+
103
+ input[type="range"]:focus::-webkit-slider-thumb {
104
+ box-shadow:
105
+ 0 0 0 4px rgba(37, 99, 235, 0.35),
106
+ 0 4px 10px rgba(0, 0, 0, 0.25);
107
+ border-color: #2563eb;
108
+ }
109
+
110
+ input[type="range"]:hover::-webkit-slider-thumb {
111
+ transform: scale(1.05);
112
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
113
+ }
@@ -0,0 +1,69 @@
1
+ <div class="header">
2
+ <h2 class="title" id="controllerTitle">Typography controls</h2>
3
+ <span class="badge">v1.3.0</span>
4
+ </div>
5
+
6
+ <div aria-live="polite" class="sr-only" id="liveRegion"></div>
7
+
8
+ <div class="group" id="groupFontSize" role="group" aria-labelledby="labelFontSize">
9
+ <div class="label-row">
10
+ <label id="labelFontSize" for="fontSize">Font size</label>
11
+ <span class="value" id="fontSizeValue" aria-hidden="true"></span>
12
+ </div>
13
+ <input type="range" id="fontSize" min="12" max="60" value="24"
14
+ aria-valuemin="12" aria-valuemax="60" aria-valuenow="24"
15
+ aria-labelledby="labelFontSize">
16
+ </div>
17
+
18
+ <div class="group" id="groupLetterSpacing" role="group" aria-labelledby="labelLetterSpacing">
19
+ <div class="label-row">
20
+ <label id="labelLetterSpacing" for="letterSpacing">Letter spacing</label>
21
+ <span class="value" id="letterSpacingValue" aria-hidden="true"></span>
22
+ </div>
23
+ <input type="range" id="letterSpacing" min="0" max="20" value="0"
24
+ aria-valuemin="0" aria-valuemax="20" aria-valuenow="0"
25
+ aria-labelledby="labelLetterSpacing">
26
+ </div>
27
+
28
+ <div class="group" id="groupWordSpacing" role="group" aria-labelledby="labelWordSpacing">
29
+ <div class="label-row">
30
+ <label id="labelWordSpacing" for="wordSpacing">Word spacing</label>
31
+ <span class="value" id="wordSpacingValue" aria-hidden="true"></span>
32
+ </div>
33
+ <input type="range" id="wordSpacing" min="0" max="40" value="0"
34
+ aria-valuemin="0" aria-valuemax="40" aria-valuenow="0"
35
+ aria-labelledby="labelWordSpacing">
36
+ </div>
37
+
38
+ <div class="group" id="groupLineHeight" role="group" aria-labelledby="labelLineHeight">
39
+ <div class="label-row">
40
+ <label id="labelLineHeight" for="lineHeight">Line height</label>
41
+ <span class="value" id="lineHeightValue" aria-hidden="true"></span>
42
+ </div>
43
+ <input type="range" id="lineHeight" min="1" max="3" step="0.1" value="1.5"
44
+ aria-valuemin="1" aria-valuemax="3" aria-valuenow="1.5"
45
+ aria-labelledby="labelLineHeight">
46
+ </div>
47
+
48
+ <div class="group" id="groupContrast" role="group" aria-labelledby="labelContrast">
49
+ <div class="label-row">
50
+ <label id="labelContrast" for="contrast">Contrast</label>
51
+ <span class="value" id="contrastValue" aria-hidden="true"></span>
52
+ </div>
53
+ <input type="range" id="contrast" min="50" max="200" value="100"
54
+ aria-valuemin="50" aria-valuemax="200" aria-valuenow="100"
55
+ aria-labelledby="labelContrast">
56
+ </div>
57
+
58
+ <div class="group" id="groupFontFamily" role="group" aria-labelledby="labelFontFamily">
59
+ <div class="label-row">
60
+ <label id="labelFontFamily" for="fontFamily">Font family</label>
61
+ </div>
62
+ <select id="fontFamily" aria-labelledby="labelFontFamily">
63
+ <option value="system-ui, sans-serif">System</option>
64
+ <option value="Georgia, serif">Serif</option>
65
+ <option value="'Courier New', monospace">Monospace</option>
66
+ <option value="Verdana, sans-serif">Verdana</option>
67
+ <option value="inherit">Inherit</option>
68
+ </select>
69
+ </div>
@@ -2,194 +2,62 @@ class TypographyController extends HTMLElement {
2
2
  constructor() {
3
3
  super();
4
4
  this.attachShadow({ mode: "open" });
5
+ }
6
+
7
+ async connectedCallback() {
8
+ // 1. Load external HTML + CSS
9
+ const [html, css] = await Promise.all([
10
+ fetch("../src//typography-controller.html").then(r => r.text()),
11
+ fetch("../src/typography-controller.css").then(r => r.text())
12
+ ]);
5
13
 
14
+ // 2. Inject into shadow DOM
6
15
  this.shadowRoot.innerHTML = `
7
- <style>
8
- :host {
9
- display: block;
10
- font-family: system-ui, sans-serif;
11
- max-width: 420px;
12
- padding: 18px 20px;
13
- border-radius: 18px;
14
- background: rgba(255, 255, 255, 0.65);
15
- border: 1px solid rgba(0, 0, 0, 0.08);
16
- backdrop-filter: blur(14px);
17
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
18
- color: #0f172a;
19
- }
20
-
21
- .header {
22
- display: flex;
23
- justify-content: space-between;
24
- align-items: baseline;
25
- margin-bottom: 14px;
26
- }
27
-
28
- .title {
29
- font-size: 14px;
30
- font-weight: 600;
31
- letter-spacing: 0.08em;
32
- text-transform: uppercase;
33
- color: #475569;
34
- }
35
-
36
- .badge {
37
- font-size: 11px;
38
- padding: 3px 8px;
39
- border-radius: 999px;
40
- background: rgba(59, 130, 246, 0.12);
41
- color: #1d4ed8;
42
- border: 1px solid rgba(59, 130, 246, 0.35);
43
- }
44
-
45
- .group {
46
- margin-bottom: 14px;
47
- }
48
-
49
- .label-row {
50
- display: flex;
51
- justify-content: space-between;
52
- align-items: center;
53
- margin-bottom: 6px;
54
- }
55
-
56
- label {
57
- font-size: 12px;
58
- font-weight: 600;
59
- color: #0f172a;
60
- }
61
-
62
- .value {
63
- font-size: 11px;
64
- color: #64748b;
65
- }
66
-
67
- input[type="range"] {
68
- -webkit-appearance: none;
69
- width: 100%;
70
- height: 6px;
71
- border-radius: 999px;
72
- background: linear-gradient(90deg, #3b82f6, #60a5fa);
73
- outline: none;
74
- }
75
-
76
- input[type="range"]::-webkit-slider-thumb {
77
- -webkit-appearance: none;
78
- height: 18px;
79
- width: 18px;
80
- border-radius: 50%;
81
- background: white;
82
- border: 1px solid rgba(0, 0, 0, 0.15);
83
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
84
- cursor: pointer;
85
- }
86
-
87
- select {
88
- width: 100%;
89
- padding: 6px 8px;
90
- border-radius: 8px;
91
- border: 1px solid rgba(148, 163, 184, 0.7);
92
- background: rgba(255, 255, 255, 0.9);
93
- font-size: 12px;
94
- color: #0f172a;
95
- outline: none;
96
- }
97
-
98
- select:focus {
99
- border-color: #2563eb;
100
- box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.4);
101
- }
102
- </style>
103
-
104
- <div class="header">
105
- <div class="title">Typography Controls</div>
106
- <div class="badge">Web Component</div>
107
- </div>
108
-
109
- <div class="group" id="groupFontSize">
110
- <div class="label-row">
111
- <label for="fontSize">Font size</label>
112
- <span class="value" id="fontSizeValue"></span>
113
- </div>
114
- <input type="range" id="fontSize" min="12" max="60" value="24">
115
- </div>
116
-
117
- <div class="group" id="groupLetterSpacing">
118
- <div class="label-row">
119
- <label for="letterSpacing">Letter spacing</label>
120
- <span class="value" id="letterSpacingValue"></span>
121
- </div>
122
- <input type="range" id="letterSpacing" min="0" max="20" value="0">
123
- </div>
124
-
125
- <div class="group" id="groupWordSpacing">
126
- <div class="label-row">
127
- <label for="wordSpacing">Word spacing</label>
128
- <span class="value" id="wordSpacingValue"></span>
129
- </div>
130
- <input type="range" id="wordSpacing" min="0" max="40" value="0">
131
- </div>
132
-
133
- <div class="group" id="groupLineHeight">
134
- <div class="label-row">
135
- <label for="lineHeight">Line height</label>
136
- <span class="value" id="lineHeightValue"></span>
137
- </div>
138
- <input type="range" id="lineHeight" min="1" max="3" step="0.1" value="1.5">
139
- </div>
140
-
141
- <div class="group" id="groupContrast">
142
- <div class="label-row">
143
- <label for="contrast">Contrast</label>
144
- <span class="value" id="contrastValue"></span>
145
- </div>
146
- <input type="range" id="contrast" min="50" max="200" value="100">
147
- </div>
148
-
149
- <div class="group" id="groupFontFamily">
150
- <div class="label-row">
151
- <label for="fontFamily">Font family</label>
152
- </div>
153
- <select id="fontFamily">
154
- <option value="system-ui, sans-serif">System</option>
155
- <option value="Georgia, serif">Serif</option>
156
- <option value="'Courier New', monospace">Monospace</option>
157
- <option value="Verdana, sans-serif">Verdana</option>
158
- </select>
159
- </div>
16
+ <style>${css}</style>
17
+ ${html}
160
18
  `;
161
- }
162
19
 
163
- connectedCallback() {
20
+ // 3. Now safe to query elements
164
21
  this.targetSelector = this.getAttribute("target");
165
22
 
166
- // Read attributes for initial hiding
23
+ // Feature toggles
167
24
  this.toggleGroup("#groupLetterSpacing", !this.hasAttribute("hide-letter-spacing"));
168
25
  this.toggleGroup("#groupWordSpacing", !this.hasAttribute("hide-word-spacing"));
169
26
  this.toggleGroup("#groupLineHeight", !this.hasAttribute("hide-line-height"));
170
27
  this.toggleGroup("#groupContrast", !this.hasAttribute("hide-contrast"));
28
+ this.toggleGroup("#groupFamily", !this.hasAttribute("hide-font-family"));
171
29
 
172
- // Query elements
30
+ // Query sliders
173
31
  this.fontSize = this.shadowRoot.querySelector("#fontSize");
174
32
  this.letterSpacing = this.shadowRoot.querySelector("#letterSpacing");
175
33
  this.wordSpacing = this.shadowRoot.querySelector("#wordSpacing");
176
34
  this.lineHeight = this.shadowRoot.querySelector("#lineHeight");
177
35
  this.contrast = this.shadowRoot.querySelector("#contrast");
178
36
  this.fontFamily = this.shadowRoot.querySelector("#fontFamily");
179
- // ⭐ Auto‑detect the user's current font family
37
+
38
+ // Auto-detect font family
180
39
  const target = this.targetElement;
181
40
  if (target) {
182
41
  const computed = getComputedStyle(target).fontFamily;
183
42
  this.fontFamily.value = computed;
184
43
  }
185
44
 
186
-
45
+ // Query value labels
187
46
  this.fontSizeValue = this.shadowRoot.querySelector("#fontSizeValue");
188
47
  this.letterSpacingValue = this.shadowRoot.querySelector("#letterSpacingValue");
189
48
  this.wordSpacingValue = this.shadowRoot.querySelector("#wordSpacingValue");
190
49
  this.lineHeightValue = this.shadowRoot.querySelector("#lineHeightValue");
191
50
  this.contrastValue = this.shadowRoot.querySelector("#contrastValue");
192
51
 
52
+ // 4. Bind value ranges
53
+ this.applyRange(this.fontSize, "font-size-min", "font-size-max");
54
+ this.applyRange(this.letterSpacing, "letter-spacing-min", "letter-spacing-max");
55
+ this.applyRange(this.wordSpacing, "word-spacing-min", "word-spacing-max");
56
+ this.applyRange(this.lineHeight, "line-height-min", "line-height-max");
57
+ this.applyRange(this.contrast, "contrast-min", "contrast-max");
58
+
59
+
60
+ // Event handler
193
61
  const updateAndEmit = () => {
194
62
  this.update();
195
63
  this.dispatchEvent(
@@ -201,6 +69,7 @@ class TypographyController extends HTMLElement {
201
69
  );
202
70
  };
203
71
 
72
+ // Bind events
204
73
  this.fontSize.addEventListener("input", updateAndEmit);
205
74
  this.letterSpacing.addEventListener("input", updateAndEmit);
206
75
  this.wordSpacing.addEventListener("input", updateAndEmit);
@@ -208,6 +77,14 @@ class TypographyController extends HTMLElement {
208
77
  this.contrast.addEventListener("input", updateAndEmit);
209
78
  this.fontFamily.addEventListener("change", updateAndEmit);
210
79
 
80
+ this.bindSlider(this.fontSize, this.fontSizeValue, "Font size");
81
+ this.bindSlider(this.letterSpacing, this.letterSpacingValue, "Letter spacing");
82
+ this.bindSlider(this.wordSpacing, this.wordSpacingValue, "Word spacing");
83
+ this.bindSlider(this.lineHeight, this.lineHeightValue, "Line height");
84
+ this.bindSlider(this.contrast, this.contrastValue, "Contrast");
85
+
86
+
87
+ // Initial update
211
88
  this.update();
212
89
  }
213
90
 
@@ -255,7 +132,16 @@ class TypographyController extends HTMLElement {
255
132
  if (values.wordSpacing !== undefined) this.wordSpacing.value = values.wordSpacing;
256
133
  if (values.lineHeight !== undefined) this.lineHeight.value = values.lineHeight;
257
134
  if (values.contrast !== undefined) this.contrast.value = values.contrast;
258
- if (values.fontFamily !== undefined) this.fontFamily.value = values.fontFamily;
135
+
136
+ if (values.fontFamily !== undefined) {
137
+ const target = this.targetElement;
138
+ if (values.fontFamily === "inherit" && target) {
139
+ const computed = getComputedStyle(target).fontFamily;
140
+ this.fontFamily.value = computed;
141
+ } else {
142
+ this.fontFamily.value = values.fontFamily;
143
+ }
144
+ }
259
145
 
260
146
  this.update();
261
147
  }
@@ -288,7 +174,54 @@ class TypographyController extends HTMLElement {
288
174
  ? this.removeAttribute("hide-contrast")
289
175
  : this.setAttribute("hide-contrast", "");
290
176
  }
177
+ if (features.fontFamily !== undefined) {
178
+ this.toggleGroup("#groupFontFamily", features.fontFamily);
179
+ features.fontFamily
180
+ ? this.removeAttribute("hide-font-family")
181
+ : this.setAttribute("hide-font-family", "");
182
+ }
291
183
  }
184
+
185
+ bindSlider(slider, valueEl, labelText) {
186
+ // Initialize visible value
187
+ valueEl.textContent = slider.value;
188
+
189
+ slider.addEventListener("input", () => {
190
+ const val = slider.value;
191
+
192
+ // Update visible value
193
+ valueEl.textContent = val;
194
+
195
+ // Update ARIA so NVDA knows the value changed
196
+ slider.setAttribute("aria-valuenow", val);
197
+
198
+ // Emit your existing event
199
+ this.update();
200
+ this.dispatchEvent(
201
+ new CustomEvent("change", {
202
+ detail: this.getValues(),
203
+ bubbles: true,
204
+ composed: true
205
+ })
206
+ );
207
+ });
208
+ }
209
+
210
+ applyRange(slider, attrMin, attrMax) {
211
+ const min = this.getAttribute(attrMin);
212
+ const max = this.getAttribute(attrMax);
213
+
214
+ if (min !== null) {
215
+ slider.min = min;
216
+ slider.setAttribute("aria-valuemin", min);
217
+ }
218
+
219
+ if (max !== null) {
220
+ slider.max = max;
221
+ slider.setAttribute("aria-valuemax", max);
222
+ }
223
+ }
224
+
292
225
  }
293
226
 
294
- customElements.define("typography-controller", TypographyController);
227
+ customElements.define("typography-controller", TypographyController);