@codesuma/baseline 1.0.8 → 1.0.10
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 +12 -2
- package/components/advanced/page.module.css +0 -1
- package/components/native/input.ts +151 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Baseline
|
|
2
2
|
|
|
3
3
|
A minimal, imperative UI framework for building fast web apps. No virtual DOM, no magic, no dependencies.
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ A minimal, imperative UI framework for building fast web apps. No virtual DOM, n
|
|
|
10
10
|
npm install @codesuma/baseline
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
## Why
|
|
13
|
+
## Why Baseline?
|
|
14
14
|
|
|
15
15
|
- **Predictable** - What you write is what happens. No hidden lifecycle, no reactivity magic.
|
|
16
16
|
- **Fast** - Direct DOM manipulation. No diffing algorithms.
|
|
@@ -66,6 +66,16 @@ import { Div, Button, Input, Span, A, Img } from '@codesuma/baseline'
|
|
|
66
66
|
const container = Div('Hello')
|
|
67
67
|
const btn = Button('Click')
|
|
68
68
|
const input = Input('Enter name...', 'text')
|
|
69
|
+
|
|
70
|
+
// Support for different input types (First arg is label for check/radio)
|
|
71
|
+
const checkbox = Input('Stay signed in', 'checkbox', { checked: true })
|
|
72
|
+
const number = Input('Age', 'number', { min: 0, max: 100 })
|
|
73
|
+
const date = Input('Date', 'date')
|
|
74
|
+
|
|
75
|
+
// Checkbox helpers
|
|
76
|
+
checkbox.isChecked() // true
|
|
77
|
+
checkbox.toggle()
|
|
78
|
+
checkbox.on('change', (checked) => console.log(checked))
|
|
69
79
|
```
|
|
70
80
|
|
|
71
81
|
### Events
|
|
@@ -1,29 +1,156 @@
|
|
|
1
|
-
import { Base } from '../base'
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { Base, IBaseComponent } from '../base'
|
|
2
|
+
|
|
3
|
+
export type InputType = 'text' | 'password' | 'email' | 'number' | 'search' | 'tel' | 'url' | 'checkbox' | 'radio' | 'date' | 'time' | 'datetime-local' | 'month' | 'week' | 'color' | 'file' | 'range' | 'hidden' | 'textarea'
|
|
4
|
+
|
|
5
|
+
export interface InputOptions {
|
|
6
|
+
value?: string | number
|
|
7
|
+
accept?: string
|
|
8
|
+
checked?: boolean
|
|
9
|
+
name?: string
|
|
10
|
+
id?: string
|
|
11
|
+
min?: string | number
|
|
12
|
+
max?: string | number
|
|
13
|
+
step?: string | number
|
|
14
|
+
autocomplete?: string
|
|
15
|
+
autofocus?: boolean
|
|
16
|
+
disabled?: boolean
|
|
17
|
+
readonly?: boolean
|
|
18
|
+
required?: boolean
|
|
19
|
+
multiple?: boolean
|
|
20
|
+
pattern?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IInputComponent extends IBaseComponent<'input'> {
|
|
24
|
+
value(): string
|
|
25
|
+
setValue(v: string): void
|
|
26
|
+
clear(): void
|
|
27
|
+
select(): void
|
|
28
|
+
focus(): void
|
|
29
|
+
blur(): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ITextAreaComponent extends IBaseComponent<'textarea'> {
|
|
33
|
+
value(): string
|
|
34
|
+
setValue(v: string): void
|
|
35
|
+
clear(): void
|
|
36
|
+
select(): void
|
|
37
|
+
focus(): void
|
|
38
|
+
blur(): void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ICheckboxComponent extends IBaseComponent<any> {
|
|
42
|
+
isChecked(): boolean
|
|
43
|
+
setChecked(v: boolean): void
|
|
44
|
+
toggle(): void
|
|
45
|
+
focus(): void
|
|
46
|
+
blur(): void
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function Input(placeholder: string, type: 'checkbox' | 'radio', options?: InputOptions): ICheckboxComponent
|
|
50
|
+
export function Input(placeholder: string, type: 'textarea', options?: InputOptions): ITextAreaComponent
|
|
51
|
+
export function Input(placeholder?: string, type?: InputType, options?: InputOptions): IInputComponent
|
|
52
|
+
export function Input(placeholder = '', type: InputType = 'text', options: InputOptions = {}) {
|
|
53
|
+
const isCheckOrRadio = type === 'checkbox' || type === 'radio'
|
|
54
|
+
const hasLabel = isCheckOrRadio && placeholder.length > 0
|
|
55
|
+
|
|
56
|
+
// If it's a checkbox/radio with a label, the main component is a Label wrapper
|
|
57
|
+
// Otherwise, it's the Input element itself
|
|
58
|
+
const base = hasLabel
|
|
59
|
+
? Base('label')
|
|
60
|
+
: (type === 'textarea' ? Base('textarea') : Base('input'))
|
|
61
|
+
|
|
62
|
+
// The actual input element to interact with
|
|
63
|
+
let inputEl: HTMLInputElement | HTMLTextAreaElement
|
|
64
|
+
|
|
65
|
+
if (hasLabel) {
|
|
66
|
+
// Create internal input for the wrapper
|
|
67
|
+
const inputComp = Base('input')
|
|
68
|
+
inputEl = inputComp.el as HTMLInputElement
|
|
69
|
+
|
|
70
|
+
// Setup wrapper styles
|
|
71
|
+
base.style({ display: 'inline-flex', alignItems: 'center', gap: '8px', cursor: 'pointer', userSelect: 'none' })
|
|
72
|
+
|
|
73
|
+
// Assemble: Input + Text
|
|
74
|
+
const text = Base('span')
|
|
75
|
+
text.el.textContent = placeholder
|
|
76
|
+
base.append(inputComp, text)
|
|
77
|
+
} else {
|
|
78
|
+
inputEl = base.el as HTMLInputElement | HTMLTextAreaElement
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Common Setup Logic (applied to inputEl) ---
|
|
82
|
+
|
|
83
|
+
if (type !== 'textarea') {
|
|
84
|
+
inputEl.setAttribute('type', type)
|
|
85
|
+
// Fix for global appearance: none which hides checkboxes
|
|
86
|
+
if (isCheckOrRadio) {
|
|
87
|
+
inputEl.style.setProperty('-webkit-appearance', 'auto')
|
|
88
|
+
inputEl.style.setProperty('appearance', 'auto')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// For non-checkboxes, placeholder goes on input. For checkboxes, it's already used as label text.
|
|
93
|
+
if (!isCheckOrRadio && placeholder) {
|
|
94
|
+
inputEl.setAttribute('placeholder', placeholder)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Attributes
|
|
98
|
+
if (options.value !== undefined) inputEl.value = String(options.value)
|
|
99
|
+
if (options.accept) inputEl.setAttribute('accept', options.accept)
|
|
100
|
+
if (options.name) inputEl.setAttribute('name', options.name)
|
|
101
|
+
// ID goes on the input element normally, but if wrapped, user might expect ID on wrapper?
|
|
102
|
+
// Standard practice: ID on input, wrapper usually wraps it.
|
|
103
|
+
if (options.id) inputEl.setAttribute('id', options.id)
|
|
104
|
+
|
|
105
|
+
if (options.min !== undefined) inputEl.setAttribute('min', String(options.min))
|
|
106
|
+
if (options.max !== undefined) inputEl.setAttribute('max', String(options.max))
|
|
107
|
+
if (options.step !== undefined) inputEl.setAttribute('step', String(options.step))
|
|
108
|
+
if (options.autocomplete) inputEl.setAttribute('autocomplete', options.autocomplete)
|
|
109
|
+
if (options.pattern) inputEl.setAttribute('pattern', options.pattern)
|
|
110
|
+
if (options.multiple) inputEl.setAttribute('multiple', '')
|
|
111
|
+
|
|
112
|
+
// Boolean properties
|
|
113
|
+
if (options.checked !== undefined && 'checked' in inputEl) (inputEl as HTMLInputElement).checked = options.checked
|
|
114
|
+
if (options.disabled) inputEl.disabled = true
|
|
115
|
+
if (options.readonly) inputEl.readOnly = true
|
|
116
|
+
if (options.required) inputEl.required = true
|
|
117
|
+
if (options.autofocus) setTimeout(() => inputEl.focus(), 0)
|
|
118
|
+
|
|
119
|
+
// Events - Proxy from inputEl to base emitter
|
|
120
|
+
inputEl.onblur = () => base.emit('blur')
|
|
121
|
+
inputEl.onfocus = () => base.emit('focus')
|
|
122
|
+
|
|
123
|
+
const emitValue = () => {
|
|
124
|
+
const val = isCheckOrRadio && 'checked' in inputEl
|
|
125
|
+
? (inputEl as HTMLInputElement).checked
|
|
126
|
+
: inputEl.value
|
|
127
|
+
return val
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
inputEl.oninput = () => base.emit('input', emitValue())
|
|
131
|
+
inputEl.onchange = () => base.emit('change', emitValue())
|
|
132
|
+
|
|
133
|
+
inputEl.onkeydown = (e: KeyboardEvent) => {
|
|
134
|
+
if (e.key === 'Enter') base.emit('enter', inputEl.value)
|
|
17
135
|
if (e.key === 'Escape') base.emit('escape')
|
|
18
|
-
base.emit('keydown', { key: e.key, value:
|
|
136
|
+
base.emit('keydown', { key: e.key, value: inputEl.value })
|
|
19
137
|
}
|
|
20
138
|
|
|
21
139
|
return Object.assign(base, {
|
|
22
|
-
focus() {
|
|
23
|
-
blur() {
|
|
24
|
-
select() {
|
|
25
|
-
value() { return
|
|
26
|
-
setValue(v: string) {
|
|
27
|
-
clear() {
|
|
28
|
-
|
|
140
|
+
focus() { inputEl.focus() },
|
|
141
|
+
blur() { inputEl.blur() },
|
|
142
|
+
select() { inputEl.select() },
|
|
143
|
+
value() { return inputEl.value },
|
|
144
|
+
setValue(v: string) { inputEl.value = v },
|
|
145
|
+
clear() { inputEl.value = '' },
|
|
146
|
+
// Checkbox/Radio specific
|
|
147
|
+
isChecked() { return 'checked' in inputEl ? (inputEl as HTMLInputElement).checked : false },
|
|
148
|
+
setChecked(v: boolean) { if ('checked' in inputEl) (inputEl as HTMLInputElement).checked = v },
|
|
149
|
+
toggle() {
|
|
150
|
+
if ('checked' in inputEl) {
|
|
151
|
+
(inputEl as HTMLInputElement).checked = !(inputEl as HTMLInputElement).checked
|
|
152
|
+
base.emit('change', (inputEl as HTMLInputElement).checked)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}) as any
|
|
29
156
|
}
|
package/package.json
CHANGED