@grayscale-dev/dragon 0.1.4 → 0.1.6

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
@@ -9,13 +9,21 @@ npm install
9
9
  npm --workspace packages/ui run build
10
10
  ```
11
11
 
12
+ **Manifest Build**
13
+
14
+ ```bash
15
+ npm --workspace packages/ui run build:manifest
16
+ ```
17
+
18
+ `build` always generates `dist/manifest.json` and validates it with zod.
19
+
12
20
  **Unit Tests**
13
21
 
14
22
  ```bash
15
23
  npm --workspace packages/ui run test
16
24
  ```
17
25
 
18
- Covers value/property sync, native `input`/`change` events, form association, focus forwarding, and styling hooks.
26
+ Covers value/property sync, native `input`/`change` events, form association, focus forwarding, styling hooks, masking behavior, and manifest integrity.
19
27
 
20
28
  **Usage (Vanilla)**
21
29
 
@@ -71,6 +79,25 @@ function handleInput(e: Event) {
71
79
  </template>
72
80
  ```
73
81
 
82
+ **Manifest Consumption**
83
+
84
+ Machine-readable docs/theme-builder metadata is exported at `@grayscale-dev/dragon/manifest`.
85
+
86
+ ```ts
87
+ import manifest from '@grayscale-dev/dragon/manifest';
88
+
89
+ for (const component of manifest.components) {
90
+ console.log(component.tag, component.cssTokens.length);
91
+ }
92
+ ```
93
+
94
+ Generate docs/theme controls from this manifest only, so package metadata remains your single source of truth.
95
+
96
+ **Metadata Files**
97
+
98
+ - `dist/manifest.json`: Dragon UI docs/theme-builder metadata (tokens, controls, builder groups, option enums).
99
+ - `custom-elements.json`: optional Web Components API standard file (not currently generated in this package).
100
+
74
101
  **Styling**
75
102
 
76
103
  CSS custom properties:
@@ -84,6 +111,12 @@ CSS custom properties:
84
111
  - `--ui-input-placeholder-color`
85
112
  - `--ui-input-focus-ring`
86
113
  - `--ui-input-label-font-size`
114
+ - `--ui-input-label-color`
115
+ - `--ui-input-floating-label-left`
116
+ - `--ui-input-floating-padding-top`
117
+ - `--ui-input-floating-padding-right`
118
+ - `--ui-input-floating-padding-bottom`
119
+ - `--ui-input-floating-padding-left`
87
120
 
88
121
  Default size values in `<dui-input>` are pixel-based.
89
122
 
@@ -109,7 +142,27 @@ dui-input::part(input) {
109
142
 
110
143
  - `label-position="above"` (default): label sits above the field.
111
144
  - `label-position="floating"`: label sits like a placeholder and floats to the top-left on focus or when the input has a value.
112
- - Floating mode hides placeholder text to avoid overlap with the label.
145
+ - Floating mode hides placeholder text to avoid overlap with the label, unless regex placeholder mode is enabled while focused.
146
+
147
+ **Masking**
148
+
149
+ `regex` enables mask formatting using a supported regex-like pattern.
150
+
151
+ - Example phone pattern: `^\(\d{3}\)\s\d{3}-\d{4}$`
152
+ - As the user types, input is normalized to the mask shape.
153
+ - `show-regex-placeholder` shows a generated placeholder such as `(xxx) xxx-xxxx`.
154
+ - With `label-position="floating"`, the regex placeholder appears only while focused.
155
+
156
+ Example:
157
+
158
+ ```html
159
+ <dui-input
160
+ label="Phone"
161
+ label-position="floating"
162
+ regex="^\(\d{3}\)\s\d{3}-\d{4}$"
163
+ show-regex-placeholder
164
+ ></dui-input>
165
+ ```
113
166
 
114
167
  **Form Behavior**
115
168
 
@@ -11,21 +11,30 @@ export declare class DuiInput extends LitElement {
11
11
  autocomplete?: string;
12
12
  label: string;
13
13
  labelPosition: 'floating' | 'above';
14
+ regex: string;
15
+ showRegexPlaceholder: boolean;
16
+ showRegexPlaceholer: boolean;
14
17
  private inputEl?;
15
18
  private internals?;
16
19
  private defaultValue?;
20
+ private mask;
21
+ private isFocused;
17
22
  constructor();
18
23
  connectedCallback(): void;
24
+ willUpdate(changed: Map<string, unknown>): void;
19
25
  updated(changed: Map<string, unknown>): void;
20
26
  focus(options?: FocusOptions): void;
21
27
  blur(): void;
22
28
  formResetCallback(): void;
23
29
  formStateRestoreCallback(state: unknown): void;
24
30
  formDisabledCallback(disabled: boolean): void;
31
+ private normalizeMaskedValue;
25
32
  private syncFormValue;
26
33
  private handleInput;
27
34
  private handleChange;
28
35
  private handleKeydown;
36
+ private handleFocus;
37
+ private handleBlur;
29
38
  render(): import("lit-html").TemplateResult<1>;
30
39
  }
31
40
  declare global {
package/dist/index.js CHANGED
@@ -1,19 +1,107 @@
1
- import { css as h, LitElement as c, html as d } from "lit";
2
- import { property as l, query as f, customElement as b } from "lit/decorators.js";
3
- import { ifDefined as u } from "lit/directives/if-defined.js";
4
- var g = Object.defineProperty, v = Object.getOwnPropertyDescriptor, a = (t, i, n, r) => {
5
- for (var o = r > 1 ? void 0 : r ? v(i, n) : i, s = t.length - 1, p; s >= 0; s--)
6
- (p = t[s]) && (o = (r ? p(i, n, o) : p(o)) || o);
7
- return r && o && g(i, n, o), o;
1
+ import { css as w, LitElement as $, html as m } from "lit";
2
+ import { property as u, query as V, customElement as P } from "lit/decorators.js";
3
+ import { ifDefined as x } from "lit/directives/if-defined.js";
4
+ var z = Object.defineProperty, R = Object.getOwnPropertyDescriptor, r = (t, e, i, s) => {
5
+ for (var l = s > 1 ? void 0 : s ? R(e, i) : e, a = t.length - 1, n; a >= 0; a--)
6
+ (n = t[a]) && (l = (s ? n(e, i, l) : n(l)) || l);
7
+ return s && l && z(e, i, l), l;
8
8
  };
9
- let e = class extends c {
9
+ function E(t) {
10
+ const e = t.trim();
11
+ if (e.startsWith("/") && e.lastIndexOf("/") > 0) {
12
+ const i = e.lastIndexOf("/");
13
+ return e.slice(1, i);
14
+ }
15
+ return e;
16
+ }
17
+ function f(t, e) {
18
+ if (t[e] !== "{")
19
+ return { count: 1, nextIndex: e };
20
+ let i = e + 1, s = "";
21
+ for (; i < t.length && /\d/.test(t[i]); )
22
+ s += t[i], i += 1;
23
+ return !s || t[i] !== "}" ? { count: 1, nextIndex: e } : {
24
+ count: Number(s),
25
+ nextIndex: i + 1
26
+ };
27
+ }
28
+ function g(t, e, i) {
29
+ const s = Number.isFinite(i) && i > 0 ? Math.floor(i) : 1;
30
+ for (let l = 0; l < s; l += 1)
31
+ e.kind === "slot" ? t.push({ kind: "slot", test: e.test, placeholder: e.placeholder }) : t.push({ kind: "literal", value: e.value });
32
+ }
33
+ function k(t) {
34
+ if (!t.trim()) return null;
35
+ let e = E(t);
36
+ if (e.startsWith("^") && (e = e.slice(1)), e.endsWith("$") && (e = e.slice(0, -1)), !e) return null;
37
+ const i = [];
38
+ for (let l = 0; l < e.length; l += 1) {
39
+ const a = e[l];
40
+ if (a === "\\") {
41
+ const h = e[l + 1];
42
+ if (!h) return null;
43
+ let d;
44
+ h === "d" ? d = { kind: "slot", test: (c) => /^\d$/.test(c), placeholder: "x" } : h === "w" ? d = { kind: "slot", test: (c) => /^\w$/.test(c), placeholder: "x" } : h === "s" ? d = { kind: "literal", value: " " } : d = { kind: "literal", value: h };
45
+ const p = f(e, l + 2);
46
+ g(i, d, p.count), l = p.nextIndex - 1;
47
+ continue;
48
+ }
49
+ if (a === "[") {
50
+ const h = e.indexOf("]", l + 1);
51
+ if (h === -1) return null;
52
+ const d = e.slice(l + 1, h);
53
+ if (!d) return null;
54
+ const p = new RegExp(`^[${d}]$`), c = {
55
+ kind: "slot",
56
+ test: (y) => p.test(y),
57
+ placeholder: "x"
58
+ }, v = f(e, h + 1);
59
+ g(i, c, v.count), l = v.nextIndex - 1;
60
+ continue;
61
+ }
62
+ if (a === ".") {
63
+ const h = {
64
+ kind: "slot",
65
+ test: (p) => /^[\s\S]$/.test(p),
66
+ placeholder: "x"
67
+ }, d = f(e, l + 1);
68
+ g(i, h, d.count), l = d.nextIndex - 1;
69
+ continue;
70
+ }
71
+ if (a === "+" || a === "*" || a === "?" || a === "{" || a === "}")
72
+ return null;
73
+ const n = { kind: "literal", value: a }, b = f(e, l + 1);
74
+ g(i, n, b.count), l = b.nextIndex - 1;
75
+ }
76
+ const s = i.map((l) => l.kind === "literal" ? l.value : l.placeholder).join("");
77
+ return {
78
+ source: e,
79
+ tokens: i,
80
+ placeholder: s
81
+ };
82
+ }
83
+ let o = class extends $ {
10
84
  constructor() {
11
- super(), this.value = "", this.placeholder = "", this.name = "", this.disabled = !1, this.required = !1, this.type = "text", this.label = "", this.labelPosition = "above", "attachInternals" in this && (this.internals = this.attachInternals());
85
+ super(), this.value = "", this.placeholder = "", this.name = "", this.disabled = !1, this.required = !1, this.type = "text", this.label = "", this.labelPosition = "above", this.regex = "", this.showRegexPlaceholder = !1, this.showRegexPlaceholer = !1, this.mask = null, this.isFocused = !1, "attachInternals" in this && (this.internals = this.attachInternals());
12
86
  }
13
87
  connectedCallback() {
14
- super.connectedCallback(), this.defaultValue === void 0 && (this.defaultValue = this.getAttribute("value") ?? ""), this.syncFormValue();
88
+ if (super.connectedCallback(), this.mask = k(this.regex), this.defaultValue === void 0) {
89
+ const t = this.getAttribute("value") ?? "";
90
+ this.defaultValue = this.normalizeMaskedValue(t);
91
+ }
92
+ this.value = this.normalizeMaskedValue(this.value), this.syncFormValue();
93
+ }
94
+ willUpdate(t) {
95
+ t.has("regex") && (this.mask = k(this.regex), this.defaultValue = this.normalizeMaskedValue(this.getAttribute("value") ?? "")), t.has("showRegexPlaceholer") && this.showRegexPlaceholer && (this.showRegexPlaceholder = !0);
15
96
  }
16
97
  updated(t) {
98
+ if (t.has("value") || t.has("regex")) {
99
+ const e = this.normalizeMaskedValue(this.value);
100
+ if (e !== this.value) {
101
+ this.value = e;
102
+ return;
103
+ }
104
+ }
17
105
  (t.has("value") || t.has("disabled")) && this.syncFormValue();
18
106
  }
19
107
  focus(t) {
@@ -26,11 +114,31 @@ let e = class extends c {
26
114
  this.value = this.defaultValue ?? "";
27
115
  }
28
116
  formStateRestoreCallback(t) {
29
- typeof t == "string" && (this.value = t);
117
+ typeof t == "string" && (this.value = this.normalizeMaskedValue(t));
30
118
  }
31
119
  formDisabledCallback(t) {
32
120
  this.disabled = t;
33
121
  }
122
+ normalizeMaskedValue(t) {
123
+ if (!this.mask) return t;
124
+ const e = this.mask.tokens.filter(
125
+ (n) => n.kind === "slot"
126
+ ), i = [];
127
+ let s = 0;
128
+ for (const n of t) {
129
+ if (s >= e.length) break;
130
+ e[s].test(n) && (i.push(n), s += 1);
131
+ }
132
+ let l = "", a = 0;
133
+ for (const n of this.mask.tokens)
134
+ if (n.kind === "slot") {
135
+ if (a >= i.length)
136
+ break;
137
+ l += i[a], a += 1;
138
+ } else
139
+ i.length > 0 && a <= i.length && (l += n.value);
140
+ return l;
141
+ }
34
142
  syncFormValue() {
35
143
  if (this.internals) {
36
144
  if (this.disabled) {
@@ -42,24 +150,33 @@ let e = class extends c {
42
150
  }
43
151
  handleInput(t) {
44
152
  t.stopPropagation();
45
- const i = t.target;
46
- this.value = i.value, this.dispatchEvent(new Event("input", { bubbles: !0, composed: !0 }));
153
+ const e = t.target, i = this.normalizeMaskedValue(e.value);
154
+ e.value !== i && (e.value = i), this.value = i, this.dispatchEvent(new Event("input", { bubbles: !0, composed: !0 }));
47
155
  }
48
156
  handleChange(t) {
49
157
  t.stopPropagation();
50
- const i = t.target;
51
- this.value = i.value, this.dispatchEvent(new Event("change", { bubbles: !0, composed: !0 }));
158
+ const e = t.target, i = this.normalizeMaskedValue(e.value);
159
+ e.value !== i && (e.value = i), this.value = i, this.dispatchEvent(new Event("change", { bubbles: !0, composed: !0 }));
52
160
  }
53
161
  handleKeydown(t) {
54
162
  if (t.key !== "Enter") return;
55
- const i = t.target;
56
- this.value = i.value, this.dispatchEvent(new Event("change", { bubbles: !0, composed: !0 }));
163
+ const e = t.target, i = this.normalizeMaskedValue(e.value);
164
+ e.value !== i && (e.value = i), this.value = i, this.dispatchEvent(new Event("change", { bubbles: !0, composed: !0 }));
165
+ }
166
+ handleFocus() {
167
+ this.isFocused = !0, this.requestUpdate();
168
+ }
169
+ handleBlur() {
170
+ this.isFocused = !1, this.requestUpdate();
57
171
  }
58
172
  render() {
59
- const t = this.label.length > 0, i = t && this.labelPosition === "floating", n = this.value.length > 0, r = i ? void 0 : this.placeholder || void 0;
60
- return d`
61
- <div class="field ${i ? "floating" : ""}" data-has-value=${n}>
62
- ${t ? d`<label for="input">${this.label}</label>` : null}
173
+ const t = this.label.length > 0, e = t && this.labelPosition === "floating", i = this.value.length > 0, s = (this.showRegexPlaceholder || this.showRegexPlaceholer) && this.mask !== null, l = s ? this.mask?.placeholder : void 0;
174
+ let a;
175
+ e ? a = s && this.isFocused ? l : void 0 : s ? a = l : a = this.placeholder || void 0;
176
+ const n = this.label || a || this.placeholder || l || void 0;
177
+ return m`
178
+ <div class="field ${e ? "floating" : ""}" data-has-value=${i}>
179
+ ${t ? m`<label for="input">${this.label}</label>` : null}
63
180
  <input
64
181
  id="input"
65
182
  part="input"
@@ -68,9 +185,11 @@ let e = class extends c {
68
185
  .name=${this.name}
69
186
  ?disabled=${this.disabled}
70
187
  ?required=${this.required}
71
- placeholder=${u(r)}
72
- autocomplete=${u(this.autocomplete)}
73
- aria-label=${u(this.label || this.placeholder || void 0)}
188
+ .placeholder=${a ?? ""}
189
+ autocomplete=${x(this.autocomplete)}
190
+ aria-label=${x(n)}
191
+ @focus=${this.handleFocus}
192
+ @blur=${this.handleBlur}
74
193
  @input=${this.handleInput}
75
194
  @change=${this.handleChange}
76
195
  @keydown=${this.handleKeydown}
@@ -79,8 +198,8 @@ let e = class extends c {
79
198
  `;
80
199
  }
81
200
  };
82
- e.formAssociated = !0;
83
- e.styles = h`
201
+ o.formAssociated = !0;
202
+ o.styles = w`
84
203
  :host {
85
204
  display: inline-block;
86
205
  width: 100%;
@@ -116,7 +235,7 @@ e.styles = h`
116
235
 
117
236
  .field.floating[data-has-value='true'] label,
118
237
  :host(:focus-within) .field.floating label {
119
- top: 7px;
238
+ top: 4px;
120
239
  transform: scale(0.85);
121
240
  color: var(--ui-input-label-color, #475569);
122
241
  }
@@ -138,10 +257,6 @@ e.styles = h`
138
257
  color: var(--ui-input-placeholder-color, #9aa4b2);
139
258
  }
140
259
 
141
- .field.floating input::placeholder {
142
- color: transparent;
143
- }
144
-
145
260
  input:focus {
146
261
  box-shadow: var(--ui-input-focus-ring, 0 0 0 3px rgba(24, 98, 255, 0.25));
147
262
  }
@@ -158,39 +273,48 @@ e.styles = h`
158
273
  padding-left: var(--ui-input-floating-padding-left, 12px);
159
274
  }
160
275
  `;
161
- a([
162
- l({ type: String })
163
- ], e.prototype, "value", 2);
164
- a([
165
- l({ type: String })
166
- ], e.prototype, "placeholder", 2);
167
- a([
168
- l({ type: String, reflect: !0 })
169
- ], e.prototype, "name", 2);
170
- a([
171
- l({ type: Boolean, reflect: !0 })
172
- ], e.prototype, "disabled", 2);
173
- a([
174
- l({ type: Boolean, reflect: !0 })
175
- ], e.prototype, "required", 2);
176
- a([
177
- l({ type: String, reflect: !0 })
178
- ], e.prototype, "type", 2);
179
- a([
180
- l({ type: String, reflect: !0 })
181
- ], e.prototype, "autocomplete", 2);
182
- a([
183
- l({ type: String })
184
- ], e.prototype, "label", 2);
185
- a([
186
- l({ type: String, attribute: "label-position" })
187
- ], e.prototype, "labelPosition", 2);
188
- a([
189
- f("input")
190
- ], e.prototype, "inputEl", 2);
191
- e = a([
192
- b("dui-input")
193
- ], e);
276
+ r([
277
+ u({ type: String })
278
+ ], o.prototype, "value", 2);
279
+ r([
280
+ u({ type: String })
281
+ ], o.prototype, "placeholder", 2);
282
+ r([
283
+ u({ type: String, reflect: !0 })
284
+ ], o.prototype, "name", 2);
285
+ r([
286
+ u({ type: Boolean, reflect: !0 })
287
+ ], o.prototype, "disabled", 2);
288
+ r([
289
+ u({ type: Boolean, reflect: !0 })
290
+ ], o.prototype, "required", 2);
291
+ r([
292
+ u({ type: String, reflect: !0 })
293
+ ], o.prototype, "type", 2);
294
+ r([
295
+ u({ type: String, reflect: !0 })
296
+ ], o.prototype, "autocomplete", 2);
297
+ r([
298
+ u({ type: String })
299
+ ], o.prototype, "label", 2);
300
+ r([
301
+ u({ type: String, attribute: "label-position" })
302
+ ], o.prototype, "labelPosition", 2);
303
+ r([
304
+ u({ type: String, reflect: !0 })
305
+ ], o.prototype, "regex", 2);
306
+ r([
307
+ u({ type: Boolean, attribute: "show-regex-placeholder", reflect: !0 })
308
+ ], o.prototype, "showRegexPlaceholder", 2);
309
+ r([
310
+ u({ type: Boolean, attribute: "show-regex-placeholer", reflect: !0 })
311
+ ], o.prototype, "showRegexPlaceholer", 2);
312
+ r([
313
+ V("input")
314
+ ], o.prototype, "inputEl", 2);
315
+ o = r([
316
+ P("dui-input")
317
+ ], o);
194
318
  export {
195
- e as DuiInput
319
+ o as DuiInput
196
320
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ComponentManifest } from '../schema.js';
2
+ export declare const duiInputManifest: ComponentManifest;
@@ -0,0 +1,5 @@
1
+ import { type DragonManifest } from './schema.js';
2
+ export declare const manifest: DragonManifest;
3
+ export declare function getManifest(): DragonManifest;
4
+ export { ManifestSchema, AttributeMetaSchema, PropertyMetaSchema, EventMetaSchema, PartMetaSchema, CssTokenMetaSchema, BuilderMetaSchema, ComponentManifestSchema } from './schema.js';
5
+ export type { DragonManifest, ComponentManifest, AttributeMeta, PropertyMeta, EventMeta, PartMeta, CssTokenMeta, BuilderMeta, BuilderControl } from './schema.js';