@studious-creative/yumekit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,178 @@
1
+ // helpers/slot-utils.js
2
+ function hideEmptySlotContainers(shadowRoot, slotsConfig = {}) {
3
+ Object.entries(slotsConfig).forEach(([slotName, containerSelector]) => {
4
+ const slot = shadowRoot.querySelector(
5
+ `slot${slotName ? `[name="${slotName}"]` : ":not([name])"}`,
6
+ );
7
+ const container = shadowRoot.querySelector(containerSelector);
8
+
9
+ if (slot && container) {
10
+ const assigned = slot
11
+ .assignedNodes({ flatten: true })
12
+ .filter((n) => {
13
+ return !(
14
+ n.nodeType === Node.TEXT_NODE &&
15
+ n.textContent.trim() === ""
16
+ );
17
+ });
18
+
19
+ container.style.display = assigned.length > 0 ? "" : "none";
20
+ }
21
+ });
22
+ }
23
+
24
+ class YumeCard extends HTMLElement {
25
+ static get observedAttributes() {
26
+ return ["color", "raised"];
27
+ }
28
+
29
+ constructor() {
30
+ super();
31
+ this.attachShadow({ mode: "open" });
32
+ this.render();
33
+ }
34
+
35
+ connectedCallback() {
36
+ this.updateColorStyles();
37
+ this.updateElevationStyles();
38
+ }
39
+
40
+ attributeChangedCallback(name, oldValue, newValue) {
41
+ if (oldValue !== newValue) {
42
+ if (name === "color") {
43
+ this.updateColorStyles();
44
+ }
45
+ if (name === "raised") {
46
+ this.updateElevationStyles();
47
+ }
48
+ this.render();
49
+ }
50
+ }
51
+
52
+ updateColorStyles() {
53
+ const color = this.getAttribute("color") || "base";
54
+
55
+ const colorVars = {
56
+ primary: [
57
+ "--base-content--",
58
+ "--primary-background-component",
59
+ "--primary-background-border",
60
+ "--primary-background-active",
61
+ ],
62
+ secondary: [
63
+ "--base-content--",
64
+ "--secondary-background-component",
65
+ "--secondary-background-border",
66
+ "--secondary-background-active",
67
+ ],
68
+ base: [
69
+ "--base-content--",
70
+ "--base-background-component",
71
+ "--base-background-border",
72
+ "--base-background-active",
73
+ ],
74
+ success: [
75
+ "--base-content--",
76
+ "--success-background-component",
77
+ "--success-background-border",
78
+ "--success-background-active",
79
+ ],
80
+ error: [
81
+ "--base-content--",
82
+ "--error-background-component",
83
+ "--error-background-border",
84
+ "--error-background-active",
85
+ ],
86
+ warning: [
87
+ "--base-content--",
88
+ "--warning-background-component",
89
+ "--warning-background-border",
90
+ "--warning-background-active",
91
+ ],
92
+ };
93
+
94
+ const selected = colorVars[color] || colorVars.base;
95
+
96
+ this.style.setProperty("--card-content-color", `var(${selected[0]})`);
97
+ this.style.setProperty("--card-border-color", `var(${selected[2]})`);
98
+ this.style.setProperty("--card-background", `var(${selected[1]})`);
99
+ this.style.setProperty(
100
+ "--card-section-background",
101
+ `var(${selected[2]})`,
102
+ );
103
+ }
104
+
105
+ updateElevationStyles() {
106
+ const isRaised = this.hasAttribute("raised");
107
+
108
+ if (isRaised) {
109
+ this.style.setProperty("--card-border-width", "0");
110
+ this.style.setProperty("--card-box-shadow", "var(--base-shadow)");
111
+ } else {
112
+ this.style.setProperty(
113
+ "--card-border-width",
114
+ "var(--component-card-border-width)",
115
+ );
116
+ this.style.setProperty("--card-box-shadow", "none");
117
+ }
118
+ }
119
+
120
+ render() {
121
+ const sheet = new CSSStyleSheet();
122
+ sheet.replaceSync(`
123
+ :host {
124
+ display: block;
125
+ box-sizing: border-box;
126
+ background: var(--card-background, var(--base-background-component));
127
+ border: var(--card-border-width, var(--component-card-border-width)) solid var(--card-border-color, var(--base-background-border));
128
+ border-radius: var(--component-card-border-radius-outer);
129
+ font-family: var(--font-family-body);
130
+ color: var(--card-content-color, var(--base-content--));
131
+ box-shadow: var(--card-box-shadow, none);
132
+ }
133
+
134
+ .header {
135
+ padding: var(--component-card-padding-outer);
136
+ border-bottom: var(--component-card-inner-border-width) solid var(--card-border-color, var(--base-background-border));
137
+ }
138
+
139
+ .body {
140
+ padding: var(--component-card-padding-outer);
141
+ }
142
+
143
+ .footer {
144
+ padding: var(--component-card-padding-inner) var(--component-card-padding-outer);
145
+ border-top: var(--component-card-inner-border-width) solid var(--card-border-color, var(--base-background-border));
146
+ }
147
+
148
+ ::slotted(*) {
149
+ margin: 0;
150
+ }
151
+ `);
152
+
153
+ this.shadowRoot.adoptedStyleSheets = [sheet];
154
+
155
+ this.shadowRoot.innerHTML = `
156
+ <div class="header" part="header">
157
+ <slot name="header"></slot>
158
+ </div>
159
+ <div class="body" part="body">
160
+ <slot></slot>
161
+ </div>
162
+ <div class="footer" part="footer">
163
+ <slot name="footer"></slot>
164
+ </div>
165
+ `;
166
+
167
+ hideEmptySlotContainers(this.shadowRoot, {
168
+ header: ".header",
169
+ footer: ".footer",
170
+ });
171
+ }
172
+ }
173
+
174
+ if (!customElements.get("y-card")) {
175
+ customElements.define("y-card", YumeCard);
176
+ }
177
+
178
+ export { YumeCard };
@@ -0,0 +1,246 @@
1
+ /* ================================================================== */
2
+ /* Centralized SVG icon strings for the YumeKit component library. */
3
+ /* */
4
+ /* Each static icon also lives in its own .svg file in this directory */
5
+ /* so it can be used standalone (e.g. <img src="…">, CSS background, */
6
+ /* design tools, etc.). The strings below mirror those files — keep */
7
+ /* them in sync when editing an icon. */
8
+ /* ================================================================== */
9
+
10
+
11
+ /* ── Checkbox marks ───────────────────────────────────────────────── */
12
+
13
+ const checkmark = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><polyline points="5 13 10 17 19 6" fill="none" stroke="currentColor" stroke-width="2"/></svg>`;
14
+
15
+ const indeterminate = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="4" y="11" width="16" height="2" rx="1" ry="1" fill="currentColor"/></svg>`;
16
+
17
+ class YumeCheckbox extends HTMLElement {
18
+ static formAssociated = true;
19
+
20
+ static get observedAttributes() {
21
+ return [
22
+ "checked",
23
+ "disabled",
24
+ "indeterminate",
25
+ "label-position",
26
+ "name",
27
+ "value",
28
+ ];
29
+ }
30
+
31
+ constructor() {
32
+ super();
33
+ this._internals = this.attachInternals();
34
+ this.attachShadow({ mode: "open" });
35
+ this.render();
36
+ }
37
+
38
+ connectedCallback() {
39
+ if (!this.hasAttribute("label-position")) {
40
+ this.setAttribute("label-position", "right");
41
+ }
42
+
43
+ this._internals.setFormValue(this.checked ? this.value : null);
44
+ }
45
+
46
+ attributeChangedCallback(name) {
47
+ if (name === "checked" || name === "value") {
48
+ this._internals.setFormValue(this.checked ? this.value : null);
49
+ }
50
+
51
+ if (name === "indeterminate") {
52
+ this.updateIcon();
53
+ }
54
+
55
+ if (name === "label-position") {
56
+ this.render();
57
+ }
58
+
59
+ this.updateState();
60
+ }
61
+
62
+ get checked() {
63
+ return this.hasAttribute("checked");
64
+ }
65
+
66
+ set checked(val) {
67
+ if (val) this.setAttribute("checked", "");
68
+ else this.removeAttribute("checked");
69
+ }
70
+
71
+ get disabled() {
72
+ return this.hasAttribute("disabled");
73
+ }
74
+
75
+ set disabled(val) {
76
+ if (val) this.setAttribute("disabled", "");
77
+ else this.removeAttribute("disabled");
78
+ }
79
+
80
+ get indeterminate() {
81
+ return this.hasAttribute("indeterminate");
82
+ }
83
+
84
+ set indeterminate(val) {
85
+ if (val) this.setAttribute("indeterminate", "");
86
+ else this.removeAttribute("indeterminate");
87
+ }
88
+
89
+ get value() {
90
+ return this.getAttribute("value") || "on";
91
+ }
92
+
93
+ set value(val) {
94
+ this.setAttribute("value", val);
95
+ }
96
+
97
+ get name() {
98
+ return this.getAttribute("name");
99
+ }
100
+
101
+ toggle() {
102
+ if (this.disabled) return;
103
+ if (this.indeterminate) {
104
+ this.indeterminate = false;
105
+ this.checked = true;
106
+ } else {
107
+ this.checked = !this.checked;
108
+ }
109
+
110
+ this.dispatchEvent(
111
+ new Event("change", { bubbles: true, composed: true }),
112
+ );
113
+ }
114
+
115
+ updateIcon() {
116
+ const icon = this.shadowRoot.querySelector(".icon");
117
+ if (!icon) return;
118
+
119
+ if (this.indeterminate) {
120
+ icon.innerHTML = indeterminate;
121
+ } else if (this.checked) {
122
+ icon.innerHTML = checkmark;
123
+ } else {
124
+ icon.innerHTML = "";
125
+ }
126
+ }
127
+
128
+ updateState() {
129
+ const box = this.shadowRoot.querySelector(".checkbox");
130
+ box.setAttribute(
131
+ "aria-checked",
132
+ this.indeterminate ? "mixed" : this.checked ? "true" : "false",
133
+ );
134
+ this.updateIcon();
135
+ }
136
+
137
+ render() {
138
+ const labelPosition = this.getAttribute("label-position") || "right";
139
+ const isDisabled = this.disabled;
140
+
141
+ const sheet = new CSSStyleSheet();
142
+ sheet.replaceSync(`
143
+ :host {
144
+ display: inline-flex;
145
+ align-items: center;
146
+ line-height: 1;
147
+ vertical-align: middle;
148
+ font-family: var(--font-family-body);
149
+ cursor: ${isDisabled ? "not-allowed" : "pointer"};
150
+ opacity: ${isDisabled ? "0.6" : "1"};
151
+ }
152
+
153
+ .wrapper {
154
+ display: inline-flex;
155
+ align-items: center;
156
+ gap: var(--spacing-x-small, 6px);
157
+ line-height: 1;
158
+ flex-direction: ${
159
+ labelPosition === "top"
160
+ ? "column"
161
+ : labelPosition === "bottom"
162
+ ? "column-reverse"
163
+ : labelPosition === "left"
164
+ ? "row-reverse"
165
+ : "row"
166
+ };
167
+ }
168
+
169
+ .checkbox {
170
+ width: var(--component-checkbox-size, 20px);
171
+ height: var(--component-checkbox-size, 20px);
172
+ border: var(--component-inputs-border-width, 2px) solid var(--component-checkbox-border-color);
173
+ border-radius: var(--component-inputs-border-radius-outer, 4px);
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ background: var(--component-checkbox-background);
178
+ box-sizing: border-box;
179
+ transition: border-color 0.2s ease;
180
+ line-height: 0;
181
+ }
182
+
183
+ .checkbox:hover {
184
+ border-color: var(--component-checkbox-accent);
185
+ }
186
+
187
+ .checkbox svg {
188
+ width: var(--component-checkbox-icon-size, 16px);
189
+ height: var(--component-checkbox-icon-size, 16px);
190
+ stroke: var(--component-checkbox-accent);
191
+ display: block;
192
+ }
193
+
194
+ .label {
195
+ display: inline-flex;
196
+ align-items: center;
197
+ font-size: 0.9em;
198
+ line-height: 1;
199
+ color: var(--component-checkbox-color);
200
+ }
201
+
202
+ .icon {
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ width: 100%;
207
+ height: 100%;
208
+ line-height: 0;
209
+ }
210
+
211
+ `);
212
+
213
+ this.shadowRoot.adoptedStyleSheets = [sheet];
214
+
215
+ this.shadowRoot.innerHTML = `
216
+ <div class="wrapper">
217
+ <div class="checkbox" role="checkbox" tabindex="0">
218
+ <span class="icon"></span>
219
+ </div>
220
+ <label part="label">
221
+ <slot></slot>
222
+ </label>
223
+ </div>
224
+ `;
225
+
226
+ this.shadowRoot
227
+ .querySelector(".checkbox")
228
+ .addEventListener("click", () => this.toggle());
229
+ this.shadowRoot
230
+ .querySelector(".checkbox")
231
+ .addEventListener("keydown", (e) => {
232
+ if (e.key === " " || e.key === "Enter") {
233
+ e.preventDefault();
234
+ this.toggle();
235
+ }
236
+ });
237
+
238
+ this.updateState();
239
+ }
240
+ }
241
+
242
+ if (!customElements.get("y-checkbox")) {
243
+ customElements.define("y-checkbox", YumeCheckbox);
244
+ }
245
+
246
+ export { YumeCheckbox };
@@ -0,0 +1,213 @@
1
+ class YumeDialog extends HTMLElement {
2
+ static get observedAttributes() {
3
+ return ["visible", "anchor", "closable"];
4
+ }
5
+
6
+ constructor() {
7
+ super();
8
+ this.attachShadow({ mode: "open" });
9
+ this.onKeyDown = this.onKeyDown.bind(this);
10
+ this.onAnchorClick = this.onAnchorClick.bind(this);
11
+ }
12
+
13
+ connectedCallback() {
14
+ this.render();
15
+ this.setupAnchor();
16
+ if (this.visible) this.show();
17
+ }
18
+
19
+ attributeChangedCallback(name, oldValue, newValue) {
20
+ if (oldValue === newValue) return;
21
+ if (name === "visible") {
22
+ this.visible ? this.show() : this.hide();
23
+ }
24
+ if (name === "anchor") {
25
+ this.setupAnchor();
26
+ }
27
+ if (name === "closable") {
28
+ this.render();
29
+ }
30
+ }
31
+
32
+ get visible() {
33
+ return this.hasAttribute("visible");
34
+ }
35
+
36
+ set visible(val) {
37
+ if (val) this.setAttribute("visible", "");
38
+ else this.removeAttribute("visible");
39
+ }
40
+
41
+ get anchor() {
42
+ return this.getAttribute("anchor");
43
+ }
44
+
45
+ set anchor(id) {
46
+ this.setAttribute("anchor", id);
47
+ }
48
+
49
+ get closable() {
50
+ return this.hasAttribute("closable");
51
+ }
52
+ set closable(val) {
53
+ if (val) this.setAttribute("closable", "");
54
+ else this.removeAttribute("closable");
55
+ }
56
+
57
+ show() {
58
+ if (!this.shadowRoot.querySelector(".dialog")) {
59
+ this.render();
60
+ }
61
+
62
+ document.addEventListener("keydown", this.onKeyDown);
63
+
64
+ const dialog = this.shadowRoot.querySelector(".dialog");
65
+ if (dialog && typeof dialog.focus === "function") {
66
+ dialog.focus();
67
+ }
68
+ }
69
+
70
+ hide() {
71
+ document.removeEventListener("keydown", this.onKeyDown);
72
+ }
73
+
74
+ setupAnchor() {
75
+ if (this._anchorEl) {
76
+ this._anchorEl.removeEventListener("click", this.onAnchorClick);
77
+ }
78
+ if (this.anchor) {
79
+ const el = document.getElementById(this.anchor);
80
+ if (el) {
81
+ this._anchorEl = el;
82
+ this._anchorEl.addEventListener("click", this.onAnchorClick);
83
+ }
84
+ }
85
+ }
86
+
87
+ onAnchorClick() {
88
+ this.visible = !this.visible;
89
+ }
90
+
91
+ onKeyDown(e) {
92
+ if (e.key === "Escape" && this.visible) {
93
+ this.visible = false;
94
+ }
95
+ }
96
+
97
+ render() {
98
+ const style = document.createElement("style");
99
+ style.textContent = `
100
+ :host {
101
+ position: fixed;
102
+ top: 0; left: 0; right: 0; bottom: 0;
103
+ display: none;
104
+ align-items: center;
105
+ justify-content: center;
106
+ background: rgba(0,0,0,0.5);
107
+ z-index: 1000;
108
+ }
109
+ :host([visible]) { display: flex; }
110
+ .dialog {
111
+ background: var(--component-dialog-background);
112
+ border: var(--component-dialog-border-width, 1px) solid var(--component-dialog-border-color);
113
+ border-radius: var(--component-dialog-border-radius-outer, 4px);
114
+ max-width: 90%;
115
+ max-height: 90%;
116
+ display: flex;
117
+ flex-direction: column;
118
+ box-shadow: var(--component-dialog-shadow, 0 2px 10px rgba(0,0,0,0.3));
119
+ }
120
+ .header {
121
+ padding: var(--component-dialog-padding, var(--spacing-medium));
122
+ font-weight: bold;
123
+ border-bottom: var(--component-dialog-inner-border-width, 1px) solid var(--component-dialog-border-color);
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: space-between;
127
+ gap: var(--spacing-small, 8px);
128
+ }
129
+ .header-content {
130
+ flex: 1;
131
+ }
132
+ .close-btn {
133
+ background: none;
134
+ border: none;
135
+ cursor: pointer;
136
+ padding: var(--spacing-x-small, 4px);
137
+ color: var(--component-dialog-color, #000);
138
+ font-size: 1.25em;
139
+ line-height: 1;
140
+ border-radius: var(--component-button-border-radius-outer, 4px);
141
+ display: flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ }
145
+ .close-btn:hover {
146
+ background: var(--component-dialog-hover-background, #eee);
147
+ }
148
+ .close-btn:focus-visible {
149
+ outline: 2px solid var(--component-dialog-accent);
150
+ outline-offset: -1px;
151
+ }
152
+ .body {
153
+ padding: var(--component-dialog-padding, var(--spacing-medium));
154
+ overflow: auto;
155
+ flex: 1;
156
+ }
157
+ .footer {
158
+ padding: var(--component-dialog-padding, var(--spacing-medium));
159
+ border-top: var(--component-dialog-inner-border-width, 1px) solid var(--component-dialog-border-color);
160
+ text-align: right;
161
+ }
162
+
163
+ ::slotted(*) {
164
+ margin: 0;
165
+ }
166
+ `;
167
+
168
+ this.shadowRoot.innerHTML = "";
169
+ this.shadowRoot.appendChild(style);
170
+
171
+ const dialog = document.createElement("div");
172
+ dialog.className = "dialog";
173
+ dialog.setAttribute("role", "dialog");
174
+ dialog.setAttribute("aria-modal", "true");
175
+ dialog.setAttribute("tabindex", "-1");
176
+
177
+ const header = document.createElement("div");
178
+ header.className = "header";
179
+
180
+ const headerContent = document.createElement("div");
181
+ headerContent.className = "header-content";
182
+ headerContent.innerHTML = `<slot name="header"></slot>`;
183
+ header.appendChild(headerContent);
184
+
185
+ if (this.closable) {
186
+ const closeBtn = document.createElement("button");
187
+ closeBtn.className = "close-btn";
188
+ closeBtn.setAttribute("aria-label", "Close");
189
+ closeBtn.innerHTML = "&#10005;";
190
+ closeBtn.addEventListener("click", () => {
191
+ this.visible = false;
192
+ });
193
+ header.appendChild(closeBtn);
194
+ }
195
+
196
+ const body = document.createElement("div");
197
+ body.className = "body";
198
+ body.innerHTML = `<slot name="body"></slot>`;
199
+
200
+ const footer = document.createElement("div");
201
+ footer.className = "footer";
202
+ footer.innerHTML = `<slot name="footer"></slot>`;
203
+
204
+ dialog.appendChild(header);
205
+ dialog.appendChild(body);
206
+ dialog.appendChild(footer);
207
+ this.shadowRoot.appendChild(dialog);
208
+ }
209
+ }
210
+
211
+ if (!customElements.get("y-dialog")) {
212
+ customElements.define("y-dialog", YumeDialog);
213
+ }