@mhmo91/schmancy 0.9.6 → 0.9.7

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/package.json CHANGED
@@ -1,17 +1,21 @@
1
1
  {
2
2
  "name": "@mhmo91/schmancy",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "description": "UI library build with web components",
5
5
  "main": "./dist/index.js",
6
6
  "customElements": "custom-elements.json",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./types/src/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs",
10
12
  "default": "./dist/index.js"
11
13
  },
12
14
  "./mixins": {
13
15
  "types": "./types/mixins/index.d.ts",
14
- "default": "./mixins/index.ts"
16
+ "import": "./dist/mixins.js",
17
+ "require": "./dist/mixins.cjs",
18
+ "default": "./dist/mixins.js"
15
19
  },
16
20
  "./*": {
17
21
  "types": "./types/src/*/index.d.ts",
@@ -35,7 +39,6 @@
35
39
  "dist",
36
40
  "types",
37
41
  "src",
38
- "mixins",
39
42
  ".claude-plugin",
40
43
  "skills",
41
44
  "custom-elements.json",
@@ -1,190 +0,0 @@
1
- import type { Constructor } from './constructor'
2
- import { LitElement } from 'lit'
3
- import { Subject, fromEvent, Observable } from 'rxjs'
4
- import { takeUntil } from 'rxjs/operators'
5
- import { classMap } from 'lit/directives/class-map.js'
6
- import { styleMap } from 'lit/directives/style-map.js'
7
- import { discoverComponent, DISCOVER_EVENT, DISCOVER_RESPONSE_EVENT, type DiscoverRequest } from './discovery.service'
8
- import { consume } from '@lit/context'
9
- import { themeContext } from '../src/theme/context'
10
- import type { TSchmancyTheme } from '../src/theme/theme.interface'
11
-
12
- export declare class IBaseMixin {
13
- disconnecting: Subject<boolean>
14
- classMap: typeof classMap
15
- styleMap: typeof styleMap
16
- discover<T extends HTMLElement>(tag: string): Observable<T | null>
17
- readonly stableId: string
18
- uid: string
19
- /**
20
- * Current locale from theme context. Use with Intl.NumberFormat/DateTimeFormat.
21
- * Defaults to navigator.language if no theme provider is found.
22
- * @example new Intl.NumberFormat(this.locale).format(1234.56)
23
- */
24
- readonly locale: string
25
- dispatchScopedEvent<T>(eventName: string, detail?: T, options?: { bubbles?: boolean; composed?: boolean }): void
26
- }
27
-
28
- export const BaseElement = <T extends Constructor<LitElement>>(superClass: T) => {
29
- class BaseElement extends superClass {
30
- disconnecting = new Subject<boolean>()
31
- private _stableId?: string
32
- private _uid?: string
33
-
34
- @consume({ context: themeContext, subscribe: true })
35
- private _theme?: Partial<TSchmancyTheme>
36
-
37
- /** Current locale from theme context. Falls back to navigator.language. */
38
- get locale(): string {
39
- return this._theme?.locale ?? (typeof navigator !== 'undefined' ? navigator.language : 'de-DE')
40
- }
41
-
42
- /** Stable ID from DOM path - lazy, only computed on first access */
43
- get stableId(): string {
44
- if (this._stableId) return this._stableId
45
- const path: string[] = []
46
- for (let el: Element | null = this; el?.parentElement && path.length < 5; el = el.parentElement) {
47
- const tag = el.tagName.toLowerCase()
48
- const siblings = Array.from(el.parentElement.children).filter(c => c.tagName === el!.tagName)
49
- path.unshift(siblings.length > 1 ? `${tag}:nth-of-type(${siblings.indexOf(el) + 1})` : tag)
50
- }
51
- const hash = Array.from(path.join('>')).reduce((h, c) => Math.imul(31, h) + c.charCodeAt(0) | 0, 0)
52
- return this._stableId = `el-${Math.abs(hash).toString(36)}`
53
- }
54
-
55
- /**
56
- * Unique instance ID - can be overridden via attribute, otherwise auto-generated.
57
- * Usage: <my-component uid="custom-id"> or let it auto-generate
58
- */
59
- get uid(): string {
60
- // Check if uid was set via attribute
61
- const attrUid = this.getAttribute('uid')
62
- if (attrUid) return attrUid
63
-
64
- // Auto-generate if not set
65
- if (!this._uid) {
66
- this._uid = `el-${crypto.randomUUID()}`
67
- }
68
- return this._uid
69
- }
70
-
71
- set uid(value: string) {
72
- if (value) {
73
- this.setAttribute('uid', value)
74
- } else {
75
- this.removeAttribute('uid')
76
- }
77
- }
78
-
79
- /**
80
- * Dispatch an event scoped to this component instance.
81
- * Emits BOTH scoped event (eventName::uid) and generic event for backward compatibility.
82
- * This prevents event collision between multiple instances of the same component.
83
- */
84
- dispatchScopedEvent<T>(eventName: string, detail?: T, options: { bubbles?: boolean; composed?: boolean } = {}): void {
85
- const { bubbles = false, composed = true } = options
86
-
87
- // Emit scoped event for new code
88
- this.dispatchEvent(
89
- new CustomEvent(`${eventName}::${this.uid}`, {
90
- detail,
91
- bubbles,
92
- composed,
93
- })
94
- )
95
-
96
- // Emit generic event for backward compatibility
97
- this.dispatchEvent(
98
- new CustomEvent(eventName, {
99
- detail,
100
- bubbles,
101
- composed,
102
- })
103
- )
104
- }
105
-
106
- classMap(classes: Record<string, boolean>) {
107
- const newClasses: Record<string, boolean> = {}
108
- Object.keys(classes).forEach(key => {
109
- key
110
- .trim()
111
- .split(' ')
112
- .filter(Boolean)
113
- .forEach(k => {
114
- newClasses[k] = classes[key]
115
- })
116
- })
117
- return classMap(newClasses)
118
- }
119
-
120
- styleMap(styles: Record<string, string | number>) {
121
- return styleMap(styles)
122
- }
123
-
124
- connectedCallback() {
125
- super.connectedCallback()
126
- this.setupDiscoveryResponse()
127
- }
128
-
129
- private setupDiscoveryResponse() {
130
- const tagName = this.tagName.toLowerCase()
131
- const whereAreYouEvent = `${tagName}-where-are-you`
132
- const hereIAmEvent = `${tagName}-here-i-am`
133
-
134
- // 1. Component tag discovery (e.g., 'schmancy-fancy-where-are-you')
135
- fromEvent(window, whereAreYouEvent)
136
- .pipe(takeUntil(this.disconnecting))
137
- .subscribe(() => {
138
- window.dispatchEvent(
139
- new CustomEvent(hereIAmEvent, {
140
- detail: { component: this },
141
- bubbles: true,
142
- composed: true,
143
- }),
144
- )
145
- })
146
-
147
- // 2. CSS selector discovery (e.g., '#app-card', '.my-class', '[uid="xyz"]')
148
- fromEvent<CustomEvent<DiscoverRequest>>(window, DISCOVER_EVENT)
149
- .pipe(takeUntil(this.disconnecting))
150
- .subscribe(({ detail: { selector, requestId } }) => {
151
- let found: Element | null = null
152
-
153
- // Check if selector matches this component's id or uid
154
- if (selector.startsWith('#')) {
155
- const id = selector.slice(1)
156
- if (this.id === id || this.uid === id) {
157
- found = this
158
- }
159
- }
160
-
161
- // Check our shadow DOM for matching element
162
- if (!found && this.shadowRoot) {
163
- found = this.shadowRoot.querySelector(selector)
164
- }
165
-
166
- if (found) {
167
- window.dispatchEvent(
168
- new CustomEvent(DISCOVER_RESPONSE_EVENT, {
169
- detail: { requestId, element: found },
170
- bubbles: true,
171
- composed: true,
172
- }),
173
- )
174
- }
175
- })
176
- }
177
-
178
- // Make discover public to match the interface
179
- discover<T extends HTMLElement>(tag: string): Observable<T | null> {
180
- return discoverComponent<T>(tag)
181
- }
182
-
183
- disconnectedCallback() {
184
- this.disconnecting.next(true)
185
- this.disconnecting.complete()
186
- super.disconnectedCallback()
187
- }
188
- }
189
- return BaseElement as Constructor<IBaseMixin> & T
190
- }
@@ -1,3 +0,0 @@
1
- export type Constructor<T = {}> = new (...args: any[]) => T
2
-
3
- export type AbstractConstructor<T = {}> = abstract new (...args: any[]) => T
@@ -1,221 +0,0 @@
1
- import { fromEvent, timer, race, Observable } from 'rxjs'
2
- import { takeUntil, map, defaultIfEmpty, take } from 'rxjs/operators'
3
-
4
- /**
5
- * Global discovery event names
6
- */
7
- const DISCOVER_EVENT = 'schmancy-discover'
8
- const DISCOVER_RESPONSE_EVENT = 'schmancy-discover-response'
9
-
10
- /**
11
- * Discovery request detail
12
- */
13
- interface DiscoverRequest {
14
- selector: string
15
- requestId: string
16
- }
17
-
18
- /**
19
- * Discovery response detail
20
- */
21
- interface DiscoverResponse {
22
- requestId: string
23
- element: HTMLElement
24
- }
25
-
26
- /**
27
- * Discover a component in the DOM using the WhereAreYou/HereIAm pattern.
28
- *
29
- * @param componentTag - The tag name of the component to discover (e.g., 'schmancy-navigation-rail')
30
- * @param timeout - How long to wait for a response in milliseconds (default: 100)
31
- * @returns Observable that emits the discovered component or null if not found
32
- */
33
- export function discoverComponent<T extends HTMLElement>(
34
- componentTag: string,
35
- timeout = 100,
36
- ): Observable<T | null> {
37
- const whereAreYouEvent = `${componentTag}-where-are-you`
38
- const hereIAmEvent = `${componentTag}-here-i-am`
39
-
40
- return new Observable(subscriber => {
41
- // Listen for response first (you were right!)
42
- const subscription = fromEvent<CustomEvent>(window, hereIAmEvent)
43
- .pipe(
44
- takeUntil(timer(timeout)),
45
- map(e => e.detail.component as T),
46
- defaultIfEmpty(null),
47
- )
48
- .subscribe(component => {
49
- subscriber.next(component)
50
- subscriber.complete()
51
- })
52
-
53
- // Then dispatch discovery request
54
- window.dispatchEvent(
55
- new CustomEvent(whereAreYouEvent, {
56
- bubbles: true,
57
- composed: true,
58
- }),
59
- )
60
-
61
- // Return cleanup function
62
- return () => subscription.unsubscribe()
63
- })
64
- }
65
-
66
- /**
67
- * Discover any of multiple components using race.
68
- * Returns the first component that responds.
69
- *
70
- * @param componentTags - Array of component tag names to discover
71
- * @returns Observable that emits the first discovered component or null if none found
72
- */
73
- export function discoverAnyComponent<T extends HTMLElement>(...componentTags: string[]): Observable<T | null> {
74
- if (componentTags.length === 0) {
75
- return new Observable(subscriber => {
76
- subscriber.next(null)
77
- subscriber.complete()
78
- })
79
- }
80
-
81
- return race(...componentTags.map(tag => discoverComponent<T>(tag)))
82
- }
83
-
84
- /**
85
- * Universal element discovery - finds ANY element by CSS selector across shadow DOM boundaries.
86
- * Uses event-based discovery pattern - no DOM traversal needed.
87
- *
88
- * How it works:
89
- * 1. Broadcasts a discovery request event on window
90
- * 2. All $LitElement components receive this event and check their shadow DOM
91
- * 3. If a match is found, they respond with the element
92
- *
93
- * @param selector - CSS selector (e.g., '#my-id', '.my-class', '[data-attr]')
94
- * @param timeout - How long to wait for a response in milliseconds (default: 150)
95
- * @returns Observable that emits the discovered element or null if not found
96
- *
97
- * @example
98
- * ```typescript
99
- * // Find element by ID across shadow boundaries
100
- * discoverElement('#app-card').subscribe(el => {
101
- * if (el) console.log('Found:', el)
102
- * })
103
- *
104
- * // Find element by class
105
- * discoverElement('.special-button').subscribe(el => {...})
106
- * ```
107
- */
108
- export function discoverElement<T extends HTMLElement>(
109
- selector: string,
110
- timeout = 150,
111
- ): Observable<T | null> {
112
- const requestId = `discover-${Date.now()}-${Math.random().toString(36).slice(2)}`
113
-
114
- return new Observable(subscriber => {
115
- // Listen for response first
116
- const subscription = fromEvent<CustomEvent<DiscoverResponse>>(window, DISCOVER_RESPONSE_EVENT)
117
- .pipe(
118
- takeUntil(timer(timeout)),
119
- map(e => e.detail),
120
- // Filter for our specific request
121
- map(detail => (detail.requestId === requestId ? (detail.element as T) : null)),
122
- // Only take the first non-null response
123
- take(1),
124
- defaultIfEmpty(null),
125
- )
126
- .subscribe(element => {
127
- subscriber.next(element)
128
- subscriber.complete()
129
- })
130
-
131
- // Broadcast discovery request
132
- window.dispatchEvent(
133
- new CustomEvent<DiscoverRequest>(DISCOVER_EVENT, {
134
- detail: { selector, requestId },
135
- bubbles: true,
136
- composed: true,
137
- }),
138
- )
139
-
140
- return () => subscription.unsubscribe()
141
- })
142
- }
143
-
144
- /**
145
- * Discover multiple elements matching a selector.
146
- * Collects all responses within the timeout period.
147
- *
148
- * @param selector - CSS selector
149
- * @param timeout - How long to collect responses (default: 150ms)
150
- * @returns Observable that emits array of discovered elements
151
- */
152
- export function discoverAllElements<T extends HTMLElement>(
153
- selector: string,
154
- timeout = 150,
155
- ): Observable<T[]> {
156
- const requestId = `discover-all-${Date.now()}-${Math.random().toString(36).slice(2)}`
157
- const elements: T[] = []
158
-
159
- return new Observable(subscriber => {
160
- // Collect all responses
161
- const subscription = fromEvent<CustomEvent<DiscoverResponse>>(window, DISCOVER_RESPONSE_EVENT)
162
- .pipe(takeUntil(timer(timeout)))
163
- .subscribe({
164
- next: e => {
165
- if (e.detail.requestId === requestId) {
166
- elements.push(e.detail.element as T)
167
- }
168
- },
169
- complete: () => {
170
- subscriber.next(elements)
171
- subscriber.complete()
172
- },
173
- })
174
-
175
- // Broadcast discovery request
176
- window.dispatchEvent(
177
- new CustomEvent<DiscoverRequest>(DISCOVER_EVENT, {
178
- detail: { selector, requestId },
179
- bubbles: true,
180
- composed: true,
181
- }),
182
- )
183
-
184
- return () => subscription.unsubscribe()
185
- })
186
- }
187
-
188
- /**
189
- * Smart discovery - automatically detects if input is a CSS selector or component tag.
190
- *
191
- * @param query - CSS selector (starts with #, ., [) OR component tag name
192
- * @param timeout - How long to wait (default: 150ms)
193
- * @returns Observable that emits the discovered element or null
194
- *
195
- * @example
196
- * ```typescript
197
- * // CSS selector - uses discoverElement
198
- * discover('#my-element').subscribe(...)
199
- *
200
- * // Component tag - uses discoverComponent
201
- * discover('schmancy-fancy').subscribe(...)
202
- * ```
203
- */
204
- export function discover<T extends HTMLElement>(
205
- query: string,
206
- timeout = 150,
207
- ): Observable<T | null> {
208
- // Check if it's a CSS selector (starts with #, ., or [)
209
- const isCssSelector = /^[#.\[]/.test(query)
210
-
211
- if (isCssSelector) {
212
- return discoverElement<T>(query, timeout)
213
- }
214
-
215
- // Otherwise treat as component tag name
216
- return discoverComponent<T>(query, timeout)
217
- }
218
-
219
- // Export event names for use in baseElement
220
- export { DISCOVER_EVENT, DISCOVER_RESPONSE_EVENT }
221
- export type { DiscoverRequest, DiscoverResponse }
@@ -1,255 +0,0 @@
1
- import { CSSResult, LitElement, PropertyValueMap } from 'lit'
2
- import { property } from 'lit/decorators.js'
3
- import { IBaseMixin } from './baseElement'
4
- import { Constructor } from './constructor'
5
- import { ITailwindElementMixin, TailwindElement } from './tailwind.mixin'
6
-
7
- /**
8
- * Cross-realm brand used by `<schmancy-form>` to discover form fields by
9
- * inheritance rather than tag-name allowlists. `Symbol.for` puts the symbol in
10
- * the global registry so detection works across module realms/bundles.
11
- */
12
- export const SCHMANCY_FORM_FIELD = Symbol.for('schmancy.form-field')
13
-
14
- /**
15
- * Interface defining the properties and methods that the FormFieldMixin adds.
16
- */
17
- export interface IFormFieldMixin extends Element {
18
- name: string
19
- value: string | string[] | boolean | number | undefined
20
- label: string
21
- required: boolean
22
- disabled: boolean
23
- readonly: boolean
24
- error: boolean
25
- validationMessage: string
26
- hint?: string
27
- id: string
28
-
29
- form: HTMLFormElement | null
30
-
31
- checkValidity(): boolean
32
- reportValidity(): boolean
33
- setCustomValidity(message: string): void
34
-
35
- toFormEntries(): Array<[string, FormDataEntryValue]>
36
- resetForm(): void
37
-
38
- emitChange(detail: any): void
39
- }
40
-
41
- /** Predicate used by `<schmancy-form>` to detect mixin descendants. */
42
- export function isSchmancyFormField(el: unknown): el is IFormFieldMixin {
43
- return !!el && typeof el === 'object' && (el as any).constructor?.[SCHMANCY_FORM_FIELD] === true
44
- }
45
-
46
- /**
47
- * A mixin that adds form field capabilities to a LitElement class.
48
- * Components that extend this mixin are automatically discovered and
49
- * collected by `<schmancy-form>` — no tag-name registration needed.
50
- *
51
- * Subclasses may override `toFormEntries()` to contribute multiple
52
- * name/value pairs to FormData (e.g. date-range, tag-input).
53
- *
54
- * @example
55
- * ```ts
56
- * class MyInput extends FormFieldMixin(TailwindElement(css`...`)) {
57
- * // Your component code here
58
- * }
59
- * ```
60
- */
61
- export function FormFieldMixin<T extends Constructor<LitElement>>(superClass: T) {
62
- class FormFieldMixinClass extends superClass {
63
- static formAssociated = true
64
-
65
- /** Brand for cross-realm detection by `<schmancy-form>`. */
66
- static readonly [SCHMANCY_FORM_FIELD] = true
67
-
68
- // Element internals for form association
69
- internals: ElementInternals | undefined
70
-
71
- /** Value snapshot captured at first render, used by `resetForm()`. */
72
- protected _defaultValue: string | string[] | boolean | number | undefined = undefined
73
-
74
- @property({ type: String })
75
- name: string = ''
76
-
77
- @property({ reflect: true })
78
- value: string | string[] | boolean | number | undefined = ''
79
-
80
- @property({ type: String })
81
- label: string = ''
82
-
83
- @property({ type: Boolean, reflect: true })
84
- required: boolean = false
85
-
86
- @property({ type: Boolean, reflect: true })
87
- disabled: boolean = false
88
-
89
- @property({ type: Boolean, reflect: true })
90
- readonly: boolean = false
91
-
92
- @property({ type: Boolean, reflect: true })
93
- error: boolean = false
94
-
95
- @property({ type: String })
96
- validationMessage: string = ''
97
-
98
- @property({ type: String })
99
- hint?: string
100
-
101
- @property({ reflect: true })
102
- override id: string = `schmancy-field-${Date.now()}-${Math.floor(Math.random() * 1000)}`
103
-
104
- constructor(...args: any[]) {
105
- super(...args)
106
- try {
107
- this.internals = this.attachInternals()
108
- } catch {
109
- this.internals = undefined
110
- }
111
- }
112
-
113
- /** The form this element is associated with (native FACE behavior). */
114
- get form(): HTMLFormElement | null {
115
- return this.internals?.form ?? null
116
- }
117
-
118
- protected firstUpdated(changedProps: PropertyValueMap<any>): void {
119
- super.firstUpdated?.(changedProps)
120
- if (this._defaultValue === undefined) this._defaultValue = this.value
121
- }
122
-
123
- protected willUpdate(changedProps: PropertyValueMap<any>): void {
124
- super.willUpdate(changedProps)
125
-
126
- if (changedProps.has('value')) {
127
- this.internals?.setFormValue(this.value as string | File | FormData | null)
128
- }
129
-
130
- if (changedProps.has('error') || changedProps.has('validationMessage')) {
131
- if (this.error && this.validationMessage) {
132
- this.internals?.setValidity({ customError: true }, this.validationMessage)
133
- } else {
134
- this.internals?.setValidity({})
135
- }
136
- }
137
-
138
- // Broadcast standard field states for consumer CSS: :state(invalid),
139
- // :state(required), :state(disabled), :state(readonly).
140
- if (changedProps.has('error')) {
141
- if (this.error) this.internals?.states.add('invalid')
142
- else this.internals?.states.delete('invalid')
143
- }
144
- if (changedProps.has('required')) {
145
- if (this.required) this.internals?.states.add('required')
146
- else this.internals?.states.delete('required')
147
- }
148
- if (changedProps.has('disabled')) {
149
- if (this.disabled) this.internals?.states.add('disabled')
150
- else this.internals?.states.delete('disabled')
151
- }
152
- if (changedProps.has('readonly')) {
153
- if (this.readonly) this.internals?.states.add('readonly')
154
- else this.internals?.states.delete('readonly')
155
- }
156
- }
157
-
158
- /**
159
- * Native FACE lifecycle — called by the browser when the owning form
160
- * is reset. Delegates to `resetForm()` so subclasses have one
161
- * override point for both programmatic and user-initiated resets.
162
- */
163
- formResetCallback(): void {
164
- this.resetForm()
165
- }
166
-
167
- /** Native FACE lifecycle — called when the form's disabled state changes. */
168
- formDisabledCallback(disabled: boolean): void {
169
- this.disabled = disabled
170
- }
171
-
172
- /**
173
- * Native FACE lifecycle — restore value after bfcache / form autofill.
174
- */
175
- formStateRestoreCallback(state: string | File | FormData | null): void {
176
- if (state == null) return
177
- this.value = state as any
178
- }
179
-
180
- /** Override to customize reset behavior; default restores `_defaultValue`. */
181
- resetForm(): void {
182
- this.value = this._defaultValue ?? ''
183
- this.error = false
184
- this.validationMessage = ''
185
- this.internals?.setValidity({})
186
- }
187
-
188
- /**
189
- * Contribute entries to a parent FormData. Default: a single
190
- * `[name, value]` pair when `name` is set and value is meaningful.
191
- * Override for multi-entry controls (e.g. date range).
192
- */
193
- toFormEntries(): Array<[string, FormDataEntryValue]> {
194
- if (!this.name || this.disabled) return []
195
- const v = this.value
196
- if (v === undefined || v === null || v === '') return []
197
- if (Array.isArray(v)) return v.map(item => [this.name, String(item)] as [string, FormDataEntryValue])
198
- if (typeof v === 'boolean') return v ? [[this.name, 'on']] : []
199
- return [[this.name, String(v)]]
200
- }
201
-
202
- checkValidity(): boolean {
203
- if (this.disabled) return true
204
- if (this.required && (this.value === '' || this.value === undefined || this.value === null)) {
205
- this.error = true
206
- this.validationMessage = 'This field is required'
207
- return false
208
- }
209
- return this.internals?.checkValidity() ?? true
210
- }
211
-
212
- reportValidity(): boolean {
213
- const isValid = this.checkValidity()
214
- if (!isValid) this.internals?.reportValidity()
215
- return isValid
216
- }
217
-
218
- setCustomValidity(message: string): void {
219
- this.validationMessage = message
220
- this.error = message !== ''
221
- if (message) {
222
- this.internals?.setValidity({ customError: true }, message)
223
- } else {
224
- this.internals?.setValidity({})
225
- }
226
- }
227
-
228
- emitChange(detail: any): void {
229
- if ('dispatchScopedEvent' in this && typeof this.dispatchScopedEvent === 'function') {
230
- this.dispatchScopedEvent('change', detail, { bubbles: true })
231
- } else {
232
- this.dispatchEvent(
233
- new CustomEvent('change', {
234
- detail,
235
- bubbles: true,
236
- composed: true,
237
- }),
238
- )
239
- }
240
- }
241
- }
242
-
243
- return FormFieldMixinClass as Constructor<IFormFieldMixin> & T
244
- }
245
-
246
- /**
247
- * A convenience function that composes FormFieldMixin with TailwindElement
248
- * to create a base class for Schmancy form components.
249
- */
250
- export function SchmancyFormField<T extends CSSResult>(componentStyle?: T) {
251
- return FormFieldMixin(TailwindElement(componentStyle)) as Constructor<IFormFieldMixin> &
252
- Constructor<ITailwindElementMixin> &
253
- Constructor<LitElement> &
254
- Constructor<IBaseMixin>
255
- }