nativecorejs 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.
- package/README.md +22 -0
- package/dist/components/builtinRegistry.d.ts +2 -0
- package/dist/components/builtinRegistry.js +72 -0
- package/dist/components/index.d.ts +59 -0
- package/dist/components/index.js +59 -0
- package/dist/components/loading-spinner.d.ts +5 -0
- package/dist/components/loading-spinner.js +48 -0
- package/dist/components/nc-a.d.ts +45 -0
- package/dist/components/nc-a.js +290 -0
- package/dist/components/nc-accordion.d.ts +36 -0
- package/dist/components/nc-accordion.js +186 -0
- package/dist/components/nc-alert.d.ts +11 -0
- package/dist/components/nc-alert.js +127 -0
- package/dist/components/nc-animation.d.ts +117 -0
- package/dist/components/nc-animation.js +1053 -0
- package/dist/components/nc-autocomplete.d.ts +41 -0
- package/dist/components/nc-autocomplete.js +275 -0
- package/dist/components/nc-avatar-group.d.ts +7 -0
- package/dist/components/nc-avatar-group.js +85 -0
- package/dist/components/nc-avatar.d.ts +9 -0
- package/dist/components/nc-avatar.js +127 -0
- package/dist/components/nc-badge.d.ts +7 -0
- package/dist/components/nc-badge.js +63 -0
- package/dist/components/nc-bottom-nav.d.ts +53 -0
- package/dist/components/nc-bottom-nav.js +198 -0
- package/dist/components/nc-breadcrumb.d.ts +10 -0
- package/dist/components/nc-breadcrumb.js +71 -0
- package/dist/components/nc-button.d.ts +38 -0
- package/dist/components/nc-button.js +293 -0
- package/dist/components/nc-card.d.ts +11 -0
- package/dist/components/nc-card.js +74 -0
- package/dist/components/nc-checkbox.d.ts +16 -0
- package/dist/components/nc-checkbox.js +194 -0
- package/dist/components/nc-chip.d.ts +8 -0
- package/dist/components/nc-chip.js +89 -0
- package/dist/components/nc-code.d.ts +37 -0
- package/dist/components/nc-code.js +315 -0
- package/dist/components/nc-collapsible.d.ts +33 -0
- package/dist/components/nc-collapsible.js +148 -0
- package/dist/components/nc-color-picker.d.ts +33 -0
- package/dist/components/nc-color-picker.js +265 -0
- package/dist/components/nc-copy-button.d.ts +10 -0
- package/dist/components/nc-copy-button.js +94 -0
- package/dist/components/nc-date-picker.d.ts +41 -0
- package/dist/components/nc-date-picker.js +443 -0
- package/dist/components/nc-div.d.ts +53 -0
- package/dist/components/nc-div.js +270 -0
- package/dist/components/nc-divider.d.ts +7 -0
- package/dist/components/nc-divider.js +57 -0
- package/dist/components/nc-drawer.d.ts +40 -0
- package/dist/components/nc-drawer.js +217 -0
- package/dist/components/nc-dropdown.d.ts +41 -0
- package/dist/components/nc-dropdown.js +170 -0
- package/dist/components/nc-empty-state.d.ts +5 -0
- package/dist/components/nc-empty-state.js +76 -0
- package/dist/components/nc-file-upload.d.ts +40 -0
- package/dist/components/nc-file-upload.js +336 -0
- package/dist/components/nc-form.d.ts +70 -0
- package/dist/components/nc-form.js +273 -0
- package/dist/components/nc-image.d.ts +10 -0
- package/dist/components/nc-image.js +139 -0
- package/dist/components/nc-input.d.ts +25 -0
- package/dist/components/nc-input.js +302 -0
- package/dist/components/nc-kbd.d.ts +5 -0
- package/dist/components/nc-kbd.js +34 -0
- package/dist/components/nc-menu-item.d.ts +43 -0
- package/dist/components/nc-menu-item.js +182 -0
- package/dist/components/nc-menu.d.ts +76 -0
- package/dist/components/nc-menu.js +360 -0
- package/dist/components/nc-modal.d.ts +51 -0
- package/dist/components/nc-modal.js +231 -0
- package/dist/components/nc-nav-item.d.ts +35 -0
- package/dist/components/nc-nav-item.js +142 -0
- package/dist/components/nc-number-input.d.ts +22 -0
- package/dist/components/nc-number-input.js +270 -0
- package/dist/components/nc-otp-input.d.ts +41 -0
- package/dist/components/nc-otp-input.js +227 -0
- package/dist/components/nc-pagination.d.ts +28 -0
- package/dist/components/nc-pagination.js +171 -0
- package/dist/components/nc-popover.d.ts +58 -0
- package/dist/components/nc-popover.js +301 -0
- package/dist/components/nc-progress-circular.d.ts +7 -0
- package/dist/components/nc-progress-circular.js +67 -0
- package/dist/components/nc-progress.d.ts +7 -0
- package/dist/components/nc-progress.js +109 -0
- package/dist/components/nc-radio.d.ts +13 -0
- package/dist/components/nc-radio.js +169 -0
- package/dist/components/nc-rating.d.ts +19 -0
- package/dist/components/nc-rating.js +187 -0
- package/dist/components/nc-rich-text.d.ts +43 -0
- package/dist/components/nc-rich-text.js +310 -0
- package/dist/components/nc-scroll-top.d.ts +28 -0
- package/dist/components/nc-scroll-top.js +103 -0
- package/dist/components/nc-select.d.ts +51 -0
- package/dist/components/nc-select.js +425 -0
- package/dist/components/nc-skeleton.d.ts +7 -0
- package/dist/components/nc-skeleton.js +90 -0
- package/dist/components/nc-slider.d.ts +41 -0
- package/dist/components/nc-slider.js +268 -0
- package/dist/components/nc-snackbar.d.ts +51 -0
- package/dist/components/nc-snackbar.js +200 -0
- package/dist/components/nc-splash.d.ts +25 -0
- package/dist/components/nc-splash.js +296 -0
- package/dist/components/nc-stepper.d.ts +50 -0
- package/dist/components/nc-stepper.js +236 -0
- package/dist/components/nc-switch.d.ts +14 -0
- package/dist/components/nc-switch.js +194 -0
- package/dist/components/nc-tab-item.d.ts +39 -0
- package/dist/components/nc-tab-item.js +127 -0
- package/dist/components/nc-table.d.ts +44 -0
- package/dist/components/nc-table.js +265 -0
- package/dist/components/nc-tabs.d.ts +79 -0
- package/dist/components/nc-tabs.js +519 -0
- package/dist/components/nc-tag-input.d.ts +49 -0
- package/dist/components/nc-tag-input.js +268 -0
- package/dist/components/nc-textarea.d.ts +15 -0
- package/dist/components/nc-textarea.js +164 -0
- package/dist/components/nc-time-picker.d.ts +51 -0
- package/dist/components/nc-time-picker.js +452 -0
- package/dist/components/nc-timeline.d.ts +53 -0
- package/dist/components/nc-timeline.js +171 -0
- package/dist/components/nc-tooltip.d.ts +27 -0
- package/dist/components/nc-tooltip.js +135 -0
- package/dist/core/component.d.ts +33 -0
- package/dist/core/component.js +208 -0
- package/dist/core/gpu-animation.d.ts +141 -0
- package/dist/core/gpu-animation.js +474 -0
- package/dist/core/lazyComponents.d.ts +13 -0
- package/dist/core/lazyComponents.js +73 -0
- package/dist/core/router.d.ts +55 -0
- package/dist/core/router.js +424 -0
- package/dist/core/state.d.ts +18 -0
- package/dist/core/state.js +153 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +11 -0
- package/dist/utils/cacheBuster.d.ts +9 -0
- package/dist/utils/cacheBuster.js +12 -0
- package/dist/utils/dom.d.ts +16 -0
- package/dist/utils/dom.js +70 -0
- package/dist/utils/events.d.ts +20 -0
- package/dist/utils/events.js +80 -0
- package/dist/utils/templates.d.ts +2 -0
- package/dist/utils/templates.js +2 -0
- package/package.json +53 -0
- package/src/styles/base.css +40 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { Component, defineComponent } from '../core/component.js';
|
|
2
|
+
const EYE_OPEN = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M1 10s3-6 9-6 9 6 9 6-3 6-9 6-9-6-9-6z"/><circle cx="10" cy="10" r="2.5"/></svg>`;
|
|
3
|
+
const EYE_CLOSED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M2 2l16 16M7.5 7.5A3 3 0 0012.5 12.5M4.2 4.2C2.6 5.5 1 8 1 10s3 6 9 6c2 0 3.8-.5 5.3-1.3M8 4.3A8.7 8.7 0 0110 4c6 0 9 6 9 6s-.9 1.8-2.5 3.2"/></svg>`;
|
|
4
|
+
const CLEAR_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" width="12" height="12"><path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>`;
|
|
5
|
+
const SEARCH_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><circle cx="6.5" cy="6.5" r="4"/><path d="M11 11l3 3" stroke-linecap="round"/></svg>`;
|
|
6
|
+
export class NcInput extends Component {
|
|
7
|
+
static useShadowDOM = true;
|
|
8
|
+
static get observedAttributes() {
|
|
9
|
+
return [
|
|
10
|
+
'name', 'value', 'type', 'placeholder', 'disabled', 'readonly', 'required',
|
|
11
|
+
'maxlength', 'minlength', 'pattern', 'autocomplete',
|
|
12
|
+
'size', 'variant', 'icon-left', 'icon-right',
|
|
13
|
+
'clearable', 'show-password-toggle', 'error', 'hint'
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
currentValue = '';
|
|
17
|
+
showPassword = false;
|
|
18
|
+
validationError = '';
|
|
19
|
+
handleInputEvent = (event) => {
|
|
20
|
+
const input = event.target;
|
|
21
|
+
if (!input || input.tagName !== 'INPUT')
|
|
22
|
+
return;
|
|
23
|
+
this.currentValue = input.value;
|
|
24
|
+
if (this.validationError) {
|
|
25
|
+
this.validationError = '';
|
|
26
|
+
this.render();
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.syncClearButton();
|
|
30
|
+
}
|
|
31
|
+
this.dispatchEvent(new CustomEvent('input', {
|
|
32
|
+
bubbles: true,
|
|
33
|
+
composed: true,
|
|
34
|
+
detail: { value: input.value, name: this.getAttribute('name') || '' }
|
|
35
|
+
}));
|
|
36
|
+
};
|
|
37
|
+
handleChangeEvent = (event) => {
|
|
38
|
+
const input = event.target;
|
|
39
|
+
if (!input || input.tagName !== 'INPUT')
|
|
40
|
+
return;
|
|
41
|
+
this.currentValue = input.value;
|
|
42
|
+
this.validationError = '';
|
|
43
|
+
this.render();
|
|
44
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
45
|
+
bubbles: true,
|
|
46
|
+
composed: true,
|
|
47
|
+
detail: { value: input.value, name: this.getAttribute('name') || '' }
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
handleClickEvent = (event) => {
|
|
51
|
+
const button = event.target.closest('[data-action]');
|
|
52
|
+
if (!button)
|
|
53
|
+
return;
|
|
54
|
+
if (button.dataset.action === 'toggle-password') {
|
|
55
|
+
this.showPassword = !this.showPassword;
|
|
56
|
+
this.render();
|
|
57
|
+
this.$('input')?.focus();
|
|
58
|
+
}
|
|
59
|
+
if (button.dataset.action === 'clear') {
|
|
60
|
+
const input = this.$('input');
|
|
61
|
+
this.currentValue = '';
|
|
62
|
+
this.validationError = '';
|
|
63
|
+
if (input)
|
|
64
|
+
input.value = '';
|
|
65
|
+
this.render();
|
|
66
|
+
this.$('input')?.focus();
|
|
67
|
+
this.dispatchEvent(new CustomEvent('clear', {
|
|
68
|
+
bubbles: true,
|
|
69
|
+
composed: true,
|
|
70
|
+
detail: { name: this.getAttribute('name') || '' }
|
|
71
|
+
}));
|
|
72
|
+
this.dispatchEvent(new CustomEvent('input', {
|
|
73
|
+
bubbles: true,
|
|
74
|
+
composed: true,
|
|
75
|
+
detail: { value: '', name: this.getAttribute('name') || '' }
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
get value() {
|
|
80
|
+
return this.$('input')?.value ?? this.currentValue;
|
|
81
|
+
}
|
|
82
|
+
set value(nextValue) {
|
|
83
|
+
this.currentValue = nextValue ?? '';
|
|
84
|
+
this.validationError = '';
|
|
85
|
+
if (this._mounted) {
|
|
86
|
+
this.render();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
template() {
|
|
90
|
+
if (!this._mounted) {
|
|
91
|
+
this.currentValue = this.getAttribute('value') || '';
|
|
92
|
+
}
|
|
93
|
+
const type = this.getAttribute('type') || 'text';
|
|
94
|
+
const name = this.getAttribute('name') || '';
|
|
95
|
+
const placeholder = this.getAttribute('placeholder') || '';
|
|
96
|
+
const disabled = this.hasAttribute('disabled');
|
|
97
|
+
const readonly = this.hasAttribute('readonly');
|
|
98
|
+
const required = this.hasAttribute('required');
|
|
99
|
+
const maxlength = this.getAttribute('maxlength');
|
|
100
|
+
const minlength = this.getAttribute('minlength');
|
|
101
|
+
const pattern = this.getAttribute('pattern');
|
|
102
|
+
const autocomplete = this.getAttribute('autocomplete') || 'off';
|
|
103
|
+
const iconLeft = this.getAttribute('icon-left') || (type === 'search' ? SEARCH_ICON : '');
|
|
104
|
+
const iconRight = this.getAttribute('icon-right') || '';
|
|
105
|
+
const clearable = this.hasAttribute('clearable');
|
|
106
|
+
const showToggle = this.hasAttribute('show-password-toggle') && type === 'password';
|
|
107
|
+
const error = this.getAttribute('error') || this.validationError;
|
|
108
|
+
const hint = this.getAttribute('hint') || '';
|
|
109
|
+
const hasLeft = !!iconLeft;
|
|
110
|
+
const hasRight = !!(iconRight || (clearable && this.currentValue) || showToggle);
|
|
111
|
+
const inputType = type === 'password' && this.showPassword ? 'text' : type;
|
|
112
|
+
return `
|
|
113
|
+
<style>
|
|
114
|
+
:host { display: block; width: 100%; font-family: var(--nc-font-family); }
|
|
115
|
+
.wrap { position: relative; display: flex; align-items: center; }
|
|
116
|
+
input {
|
|
117
|
+
width: 100%;
|
|
118
|
+
box-sizing: border-box;
|
|
119
|
+
padding: var(--nc-spacing-sm) var(--nc-spacing-md);
|
|
120
|
+
padding-left: ${hasLeft ? '2.4rem' : 'var(--nc-spacing-md)'};
|
|
121
|
+
padding-right: ${hasRight ? '2.4rem' : 'var(--nc-spacing-md)'};
|
|
122
|
+
background: var(--nc-bg, #ffffff);
|
|
123
|
+
border: var(--nc-input-border, 1px solid #d1d5db);
|
|
124
|
+
border-radius: var(--nc-input-radius, 0.5rem);
|
|
125
|
+
color: var(--nc-text, #111827);
|
|
126
|
+
font-size: var(--nc-font-size-base, 1rem);
|
|
127
|
+
font-family: var(--nc-font-family);
|
|
128
|
+
outline: none;
|
|
129
|
+
transition: border-color var(--nc-transition-fast, 160ms ease), box-shadow var(--nc-transition-fast, 160ms ease);
|
|
130
|
+
opacity: ${disabled ? '0.5' : '1'};
|
|
131
|
+
cursor: ${disabled ? 'not-allowed' : 'text'};
|
|
132
|
+
}
|
|
133
|
+
:host([size="sm"]) input { font-size: var(--nc-font-size-sm, 0.875rem); padding-top: var(--nc-spacing-xs, 0.25rem); padding-bottom: var(--nc-spacing-xs, 0.25rem); }
|
|
134
|
+
:host([size="lg"]) input { font-size: var(--nc-font-size-lg, 1.125rem); padding-top: var(--nc-spacing-md, 1rem); padding-bottom: var(--nc-spacing-md, 1rem); }
|
|
135
|
+
:host([variant="filled"]) input { background: var(--nc-bg-tertiary, #f3f4f6); border-color: transparent; }
|
|
136
|
+
:host([variant="filled"]) input:focus { background: var(--nc-bg, #ffffff); }
|
|
137
|
+
input:focus { border-color: var(--nc-input-focus-border, #10b981); box-shadow: 0 0 0 3px rgba(16,185,129,.15); }
|
|
138
|
+
:host([error]) input, input.has-error { border-color: var(--nc-danger, #ef4444) !important; box-shadow: 0 0 0 3px rgba(239,68,68,.12) !important; }
|
|
139
|
+
input::placeholder { color: var(--nc-text-muted, #6b7280); }
|
|
140
|
+
input[type="search"]::-webkit-search-cancel-button { display: none; }
|
|
141
|
+
.icon {
|
|
142
|
+
position: absolute;
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
justify-content: center;
|
|
146
|
+
color: var(--nc-text-muted, #6b7280);
|
|
147
|
+
pointer-events: none;
|
|
148
|
+
width: 2.2rem;
|
|
149
|
+
}
|
|
150
|
+
.icon--left { left: 0; }
|
|
151
|
+
.icon--right { right: 0; pointer-events: auto; }
|
|
152
|
+
.action-btn {
|
|
153
|
+
background: none;
|
|
154
|
+
border: none;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
padding: 4px;
|
|
157
|
+
color: var(--nc-text-muted, #6b7280);
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
transition: color var(--nc-transition-fast, 160ms ease);
|
|
161
|
+
border-radius: var(--nc-radius-sm, 4px);
|
|
162
|
+
}
|
|
163
|
+
.action-btn:hover { color: var(--nc-text, #111827); }
|
|
164
|
+
.subtext {
|
|
165
|
+
font-size: var(--nc-font-size-xs, 0.75rem);
|
|
166
|
+
margin-top: 4px;
|
|
167
|
+
display: block;
|
|
168
|
+
}
|
|
169
|
+
.subtext--hint { color: var(--nc-text-muted, #6b7280); }
|
|
170
|
+
.subtext--error { color: var(--nc-danger, #ef4444); }
|
|
171
|
+
</style>
|
|
172
|
+
|
|
173
|
+
<div class="wrap">
|
|
174
|
+
${hasLeft ? `<span class="icon icon--left">${iconLeft}</span>` : ''}
|
|
175
|
+
<input
|
|
176
|
+
type="${inputType}"
|
|
177
|
+
name="${name}"
|
|
178
|
+
value="${this.currentValue}"
|
|
179
|
+
placeholder="${placeholder}"
|
|
180
|
+
autocomplete="${autocomplete}"
|
|
181
|
+
${disabled ? 'disabled' : ''}
|
|
182
|
+
${readonly ? 'readonly' : ''}
|
|
183
|
+
${required ? 'required' : ''}
|
|
184
|
+
${maxlength ? `maxlength="${maxlength}"` : ''}
|
|
185
|
+
${minlength ? `minlength="${minlength}"` : ''}
|
|
186
|
+
${pattern ? `pattern="${pattern}"` : ''}
|
|
187
|
+
${error ? 'class="has-error"' : ''}
|
|
188
|
+
aria-invalid="${!!error}"
|
|
189
|
+
aria-describedby="${error ? 'subtext' : hint ? 'subtext' : ''}"
|
|
190
|
+
/>
|
|
191
|
+
${hasRight ? `
|
|
192
|
+
<span class="icon icon--right">
|
|
193
|
+
${showToggle ? `
|
|
194
|
+
<button class="action-btn" type="button" data-action="toggle-password" aria-label="${this.showPassword ? 'Hide password' : 'Show password'}">
|
|
195
|
+
${this.showPassword ? EYE_CLOSED : EYE_OPEN}
|
|
196
|
+
</button>` : ''}
|
|
197
|
+
${clearable && this.currentValue && !showToggle ? `
|
|
198
|
+
<button class="action-btn" type="button" data-action="clear" aria-label="Clear">
|
|
199
|
+
${CLEAR_ICON}
|
|
200
|
+
</button>` : ''}
|
|
201
|
+
${iconRight && !clearable && !showToggle ? iconRight : ''}
|
|
202
|
+
</span>` : ''}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
${error ? `<span class="subtext subtext--error" id="subtext" role="alert">${error}</span>` : ''}
|
|
206
|
+
${hint && !error ? `<span class="subtext subtext--hint" id="subtext">${hint}</span>` : ''}
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
onMount() {
|
|
210
|
+
this.shadowRoot?.addEventListener('input', this.handleInputEvent);
|
|
211
|
+
this.shadowRoot?.addEventListener('change', this.handleChangeEvent);
|
|
212
|
+
this.shadowRoot?.addEventListener('click', this.handleClickEvent);
|
|
213
|
+
}
|
|
214
|
+
onUnmount() {
|
|
215
|
+
this.shadowRoot?.removeEventListener('input', this.handleInputEvent);
|
|
216
|
+
this.shadowRoot?.removeEventListener('change', this.handleChangeEvent);
|
|
217
|
+
this.shadowRoot?.removeEventListener('click', this.handleClickEvent);
|
|
218
|
+
}
|
|
219
|
+
syncClearButton() {
|
|
220
|
+
const clearButton = this.$('[data-action="clear"]');
|
|
221
|
+
if (!clearButton)
|
|
222
|
+
return;
|
|
223
|
+
clearButton.style.display = this.currentValue ? 'flex' : 'none';
|
|
224
|
+
}
|
|
225
|
+
getInput() {
|
|
226
|
+
return this.$('input');
|
|
227
|
+
}
|
|
228
|
+
buildValidationMessage(input) {
|
|
229
|
+
const { validity } = input;
|
|
230
|
+
if (validity.valueMissing)
|
|
231
|
+
return 'This field is required.';
|
|
232
|
+
if (validity.typeMismatch) {
|
|
233
|
+
if (input.type === 'email')
|
|
234
|
+
return 'Enter a valid email address.';
|
|
235
|
+
if (input.type === 'url')
|
|
236
|
+
return 'Enter a valid URL.';
|
|
237
|
+
return 'Enter a valid value.';
|
|
238
|
+
}
|
|
239
|
+
if (validity.patternMismatch)
|
|
240
|
+
return 'Enter a value in the expected format.';
|
|
241
|
+
if (validity.tooShort) {
|
|
242
|
+
const minLength = input.getAttribute('minlength');
|
|
243
|
+
return minLength ? `Enter at least ${minLength} characters.` : 'The value is too short.';
|
|
244
|
+
}
|
|
245
|
+
if (validity.tooLong) {
|
|
246
|
+
const maxLength = input.getAttribute('maxlength');
|
|
247
|
+
return maxLength ? `Enter no more than ${maxLength} characters.` : 'The value is too long.';
|
|
248
|
+
}
|
|
249
|
+
if (validity.badInput)
|
|
250
|
+
return 'Enter a valid value.';
|
|
251
|
+
return '';
|
|
252
|
+
}
|
|
253
|
+
getValidationMessage() {
|
|
254
|
+
const explicitError = this.getAttribute('error');
|
|
255
|
+
if (explicitError)
|
|
256
|
+
return explicitError;
|
|
257
|
+
const input = this.getInput();
|
|
258
|
+
if (!input)
|
|
259
|
+
return this.validationError;
|
|
260
|
+
return this.buildValidationMessage(input);
|
|
261
|
+
}
|
|
262
|
+
checkValidity() {
|
|
263
|
+
const input = this.getInput();
|
|
264
|
+
if (!input)
|
|
265
|
+
return true;
|
|
266
|
+
return input.checkValidity();
|
|
267
|
+
}
|
|
268
|
+
validate() {
|
|
269
|
+
const isValid = this.checkValidity();
|
|
270
|
+
this.validationError = isValid ? '' : this.getValidationMessage();
|
|
271
|
+
if (this._mounted)
|
|
272
|
+
this.render();
|
|
273
|
+
return isValid;
|
|
274
|
+
}
|
|
275
|
+
reportValidity() {
|
|
276
|
+
return this.validate();
|
|
277
|
+
}
|
|
278
|
+
clearValidationError() {
|
|
279
|
+
if (!this.validationError)
|
|
280
|
+
return;
|
|
281
|
+
this.validationError = '';
|
|
282
|
+
if (this._mounted)
|
|
283
|
+
this.render();
|
|
284
|
+
}
|
|
285
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
286
|
+
if (oldValue === newValue)
|
|
287
|
+
return;
|
|
288
|
+
if (name === 'value' && this._mounted) {
|
|
289
|
+
this.currentValue = newValue || '';
|
|
290
|
+
this.validationError = '';
|
|
291
|
+
const input = this.$('input');
|
|
292
|
+
if (input)
|
|
293
|
+
input.value = this.currentValue;
|
|
294
|
+
this.syncClearButton();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (this._mounted) {
|
|
298
|
+
this.render();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
defineComponent('nc-input', NcInput);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Component, defineComponent } from '../core/component.js';
|
|
2
|
+
export class NcKbd extends Component {
|
|
3
|
+
static useShadowDOM = true;
|
|
4
|
+
template() {
|
|
5
|
+
const size = this.getAttribute('size') || 'md';
|
|
6
|
+
const padding = size === 'sm' ? '1px 5px' : size === 'lg' ? '4px 12px' : '2px 8px';
|
|
7
|
+
const fontSize = size === 'sm' ? '11px' : size === 'lg' ? '15px' : '12px';
|
|
8
|
+
return `
|
|
9
|
+
<style>
|
|
10
|
+
:host { display: inline-block; }
|
|
11
|
+
kbd {
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
font-family: var(--nc-font-family-mono, 'SFMono-Regular', Consolas, monospace);
|
|
16
|
+
font-size: ${fontSize};
|
|
17
|
+
font-weight: var(--nc-font-weight-medium, 500);
|
|
18
|
+
line-height: 1;
|
|
19
|
+
color: var(--nc-text, #111827);
|
|
20
|
+
background: var(--nc-bg-secondary, #f8fafc);
|
|
21
|
+
border: 1px solid var(--nc-border, #e5e7eb);
|
|
22
|
+
border-bottom-width: 3px;
|
|
23
|
+
border-radius: var(--nc-radius-sm, 0.375rem);
|
|
24
|
+
padding: ${padding};
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
user-select: none;
|
|
27
|
+
box-shadow: inset 0 -1px 0 rgba(0,0,0,.08);
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
30
|
+
<kbd><slot></slot></kbd>
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
defineComponent('nc-kbd', NcKbd);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeCore Menu Item Component (nc-menu-item)
|
|
3
|
+
*
|
|
4
|
+
* A single selectable row inside an nc-menu. Renders an optional icon,
|
|
5
|
+
* a label via the default slot, and an optional end slot for badges/shortcuts.
|
|
6
|
+
*
|
|
7
|
+
* Attributes:
|
|
8
|
+
* icon - URL to a leading icon image (optional)
|
|
9
|
+
* alt - Alt text for the icon (accessibility)
|
|
10
|
+
* disabled - Boolean. Prevents selection and dims the row.
|
|
11
|
+
* active - Boolean. Marks the row as currently selected. Set by nc-menu
|
|
12
|
+
* or manually for controlled menus.
|
|
13
|
+
* danger - Boolean. Applies destructive/danger coloring.
|
|
14
|
+
*
|
|
15
|
+
* Slots:
|
|
16
|
+
* default - Item label / content.
|
|
17
|
+
* end - Trailing content (badge, keyboard shortcut, chevron, etc.)
|
|
18
|
+
*
|
|
19
|
+
* Events emitted:
|
|
20
|
+
* nc-select - {} - fires when the item is clicked or activated via keyboard.
|
|
21
|
+
* Bubbles + composed so nc-menu can delegate.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* <nc-menu-item icon="/icons/edit.svg" alt="Edit">Edit</nc-menu-item>
|
|
25
|
+
* <nc-menu-item danger>Delete</nc-menu-item>
|
|
26
|
+
* <nc-menu-item disabled>Unavailable</nc-menu-item>
|
|
27
|
+
* <nc-menu-item active>Selected</nc-menu-item>
|
|
28
|
+
* <nc-menu-item>
|
|
29
|
+
* Save
|
|
30
|
+
* <span slot="end">Ctrl+S</span>
|
|
31
|
+
* </nc-menu-item>
|
|
32
|
+
*/
|
|
33
|
+
import { Component } from '../core/component.js';
|
|
34
|
+
export declare class NcMenuItem extends Component {
|
|
35
|
+
static useShadowDOM: boolean;
|
|
36
|
+
static get observedAttributes(): string[];
|
|
37
|
+
private _onClick;
|
|
38
|
+
private _onKeydown;
|
|
39
|
+
template(): string;
|
|
40
|
+
onMount(): void;
|
|
41
|
+
onUnmount(): void;
|
|
42
|
+
attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
|
|
43
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeCore Menu Item Component (nc-menu-item)
|
|
3
|
+
*
|
|
4
|
+
* A single selectable row inside an nc-menu. Renders an optional icon,
|
|
5
|
+
* a label via the default slot, and an optional end slot for badges/shortcuts.
|
|
6
|
+
*
|
|
7
|
+
* Attributes:
|
|
8
|
+
* icon - URL to a leading icon image (optional)
|
|
9
|
+
* alt - Alt text for the icon (accessibility)
|
|
10
|
+
* disabled - Boolean. Prevents selection and dims the row.
|
|
11
|
+
* active - Boolean. Marks the row as currently selected. Set by nc-menu
|
|
12
|
+
* or manually for controlled menus.
|
|
13
|
+
* danger - Boolean. Applies destructive/danger coloring.
|
|
14
|
+
*
|
|
15
|
+
* Slots:
|
|
16
|
+
* default - Item label / content.
|
|
17
|
+
* end - Trailing content (badge, keyboard shortcut, chevron, etc.)
|
|
18
|
+
*
|
|
19
|
+
* Events emitted:
|
|
20
|
+
* nc-select - {} - fires when the item is clicked or activated via keyboard.
|
|
21
|
+
* Bubbles + composed so nc-menu can delegate.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* <nc-menu-item icon="/icons/edit.svg" alt="Edit">Edit</nc-menu-item>
|
|
25
|
+
* <nc-menu-item danger>Delete</nc-menu-item>
|
|
26
|
+
* <nc-menu-item disabled>Unavailable</nc-menu-item>
|
|
27
|
+
* <nc-menu-item active>Selected</nc-menu-item>
|
|
28
|
+
* <nc-menu-item>
|
|
29
|
+
* Save
|
|
30
|
+
* <span slot="end">Ctrl+S</span>
|
|
31
|
+
* </nc-menu-item>
|
|
32
|
+
*/
|
|
33
|
+
import { Component, defineComponent } from '../core/component.js';
|
|
34
|
+
import { html } from '../utils/templates.js';
|
|
35
|
+
export class NcMenuItem extends Component {
|
|
36
|
+
static useShadowDOM = true;
|
|
37
|
+
static get observedAttributes() {
|
|
38
|
+
return ['icon', 'alt', 'disabled', 'active', 'danger'];
|
|
39
|
+
}
|
|
40
|
+
_onClick = null;
|
|
41
|
+
_onKeydown = null;
|
|
42
|
+
template() {
|
|
43
|
+
const icon = this.getAttribute('icon');
|
|
44
|
+
const alt = this.getAttribute('alt') || '';
|
|
45
|
+
const disabled = this.hasAttribute('disabled');
|
|
46
|
+
const iconHTML = icon
|
|
47
|
+
? `<img class="item__icon" src="${icon}" alt="${alt}" />`
|
|
48
|
+
: '';
|
|
49
|
+
return html `
|
|
50
|
+
<style>
|
|
51
|
+
:host {
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.item {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: var(--nc-spacing-sm);
|
|
59
|
+
padding: var(--nc-spacing-sm) var(--nc-spacing-md);
|
|
60
|
+
border-radius: var(--nc-radius-md);
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
font-family: var(--nc-font-family);
|
|
63
|
+
font-size: var(--nc-font-size-sm);
|
|
64
|
+
font-weight: var(--nc-font-weight-medium);
|
|
65
|
+
color: var(--nc-text);
|
|
66
|
+
background: transparent;
|
|
67
|
+
border: none;
|
|
68
|
+
width: 100%;
|
|
69
|
+
text-align: left;
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
transition:
|
|
72
|
+
background var(--nc-transition-fast),
|
|
73
|
+
color var(--nc-transition-fast);
|
|
74
|
+
user-select: none;
|
|
75
|
+
outline: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.item:hover {
|
|
79
|
+
background: var(--nc-bg-tertiary);
|
|
80
|
+
color: var(--nc-text);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.item:focus-visible {
|
|
84
|
+
outline: 2px solid var(--nc-primary);
|
|
85
|
+
outline-offset: -2px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Active / selected */
|
|
89
|
+
:host([active]) .item {
|
|
90
|
+
background: rgba(16, 185, 129, 0.1);
|
|
91
|
+
color: var(--nc-primary);
|
|
92
|
+
font-weight: var(--nc-font-weight-semibold);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:host([active]) .item:hover {
|
|
96
|
+
background: rgba(16, 185, 129, 0.15);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Danger */
|
|
100
|
+
:host([danger]) .item {
|
|
101
|
+
color: var(--nc-danger);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
:host([danger]) .item:hover {
|
|
105
|
+
background: rgba(239, 68, 68, 0.08);
|
|
106
|
+
color: var(--nc-danger);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Disabled */
|
|
110
|
+
:host([disabled]) .item {
|
|
111
|
+
opacity: 0.4;
|
|
112
|
+
cursor: not-allowed;
|
|
113
|
+
pointer-events: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.item__icon {
|
|
117
|
+
width: 16px;
|
|
118
|
+
height: 16px;
|
|
119
|
+
object-fit: contain;
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
opacity: 0.75;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
:host([active]) .item__icon,
|
|
125
|
+
:host([danger]) .item__icon {
|
|
126
|
+
opacity: 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.item__label {
|
|
130
|
+
flex: 1;
|
|
131
|
+
min-width: 0;
|
|
132
|
+
overflow: hidden;
|
|
133
|
+
text-overflow: ellipsis;
|
|
134
|
+
white-space: nowrap;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.item__end {
|
|
138
|
+
flex-shrink: 0;
|
|
139
|
+
font-size: var(--nc-font-size-xs, 0.7rem);
|
|
140
|
+
color: var(--nc-text-muted, var(--nc-text-secondary));
|
|
141
|
+
opacity: 0.65;
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
144
|
+
<div class="item" role="menuitem" tabindex="${disabled ? -1 : 0}" aria-disabled="${disabled}">
|
|
145
|
+
${iconHTML}
|
|
146
|
+
<span class="item__label"><slot></slot></span>
|
|
147
|
+
<span class="item__end"><slot name="end"></slot></span>
|
|
148
|
+
</div>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
onMount() {
|
|
152
|
+
this._onClick = (e) => {
|
|
153
|
+
if (this.hasAttribute('disabled')) {
|
|
154
|
+
e.stopPropagation();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.emitEvent('nc-select', {}, { bubbles: true, composed: true });
|
|
158
|
+
};
|
|
159
|
+
this._onKeydown = (e) => {
|
|
160
|
+
const ke = e;
|
|
161
|
+
if (ke.key === 'Enter' || ke.key === ' ') {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
this._onClick(e);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
this.shadowRoot.addEventListener('click', this._onClick);
|
|
167
|
+
this.shadowRoot.addEventListener('keydown', this._onKeydown);
|
|
168
|
+
}
|
|
169
|
+
onUnmount() {
|
|
170
|
+
if (this._onClick)
|
|
171
|
+
this.shadowRoot?.removeEventListener('click', this._onClick);
|
|
172
|
+
if (this._onKeydown)
|
|
173
|
+
this.shadowRoot?.removeEventListener('keydown', this._onKeydown);
|
|
174
|
+
this._onClick = null;
|
|
175
|
+
this._onKeydown = null;
|
|
176
|
+
}
|
|
177
|
+
attributeChangedCallback(_name, oldValue, newValue) {
|
|
178
|
+
if (this._mounted && oldValue !== newValue)
|
|
179
|
+
this.render();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
defineComponent('nc-menu-item', NcMenuItem);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeCore Menu Component (nc-menu)
|
|
3
|
+
*
|
|
4
|
+
* A vertical command menu. Place nc-menu-item elements inside as direct children.
|
|
5
|
+
* Supports grouped items (via nc-menu-divider or label attribute), a searchable
|
|
6
|
+
* filter box, and several visual variants.
|
|
7
|
+
*
|
|
8
|
+
* Attributes:
|
|
9
|
+
* variant - 'default' | 'compact' | 'inset' | 'bordered' (default: 'default')
|
|
10
|
+
* searchable - Boolean. Adds a filter input at the top that hides non-matching items.
|
|
11
|
+
* label - Optional header label shown above the items.
|
|
12
|
+
* width - CSS width value, e.g. '220px'. Defaults to 'fit-content'.
|
|
13
|
+
* auto-active - Boolean. Automatically moves the `active` attribute to whichever
|
|
14
|
+
* item was last selected or navigated to. For nc-a items, also
|
|
15
|
+
* matches the current path on mount.
|
|
16
|
+
*
|
|
17
|
+
* Slots:
|
|
18
|
+
* default - nc-menu-item (and nc-menu-divider) elements.
|
|
19
|
+
*
|
|
20
|
+
* Events emitted:
|
|
21
|
+
* nc-menu-select - { item: HTMLElement, label: string } - fires when any
|
|
22
|
+
* nc-menu-item inside emits nc-select.
|
|
23
|
+
*
|
|
24
|
+
* Keyboard:
|
|
25
|
+
* ArrowDown / ArrowUp - move focus between items.
|
|
26
|
+
* Home / End - jump to first / last item.
|
|
27
|
+
* Escape - blur the menu.
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* <nc-menu label="Actions">
|
|
31
|
+
* <nc-menu-item icon="/icons/edit.svg">Edit</nc-menu-item>
|
|
32
|
+
* <nc-menu-item icon="/icons/copy.svg">Duplicate</nc-menu-item>
|
|
33
|
+
* <nc-menu-item danger icon="/icons/trash.svg">Delete</nc-menu-item>
|
|
34
|
+
* </nc-menu>
|
|
35
|
+
*
|
|
36
|
+
* <nc-menu searchable variant="bordered" width="260px">
|
|
37
|
+
* <nc-menu-item>Dashboard</nc-menu-item>
|
|
38
|
+
* <nc-menu-item active>Components</nc-menu-item>
|
|
39
|
+
* <nc-menu-item>Settings</nc-menu-item>
|
|
40
|
+
* </nc-menu>
|
|
41
|
+
*
|
|
42
|
+
* <!-- Navigation sidebar: nc-a items, active managed automatically -->
|
|
43
|
+
* <nc-menu auto-active variant="inset" width="220px">
|
|
44
|
+
* <nc-a href="/dashboard" variant="ghost">Dashboard</nc-a>
|
|
45
|
+
* <nc-a href="/components" variant="ghost">Components</nc-a>
|
|
46
|
+
* <nc-a href="/settings" variant="ghost">Settings</nc-a>
|
|
47
|
+
* </nc-menu>
|
|
48
|
+
*/
|
|
49
|
+
import { Component } from '../core/component.js';
|
|
50
|
+
export declare class NcMenu extends Component {
|
|
51
|
+
static useShadowDOM: boolean;
|
|
52
|
+
static attributeOptions: {
|
|
53
|
+
variant: string[];
|
|
54
|
+
};
|
|
55
|
+
static get observedAttributes(): string[];
|
|
56
|
+
private _onSlotChange;
|
|
57
|
+
private _onSelect;
|
|
58
|
+
private _onNavigate;
|
|
59
|
+
private _onKeydown;
|
|
60
|
+
private _onSearchInput;
|
|
61
|
+
template(): string;
|
|
62
|
+
onMount(): void;
|
|
63
|
+
onUnmount(): void;
|
|
64
|
+
attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
|
|
65
|
+
private _getEnabledItems;
|
|
66
|
+
private _focusItem;
|
|
67
|
+
/** Moves `active` to `target`, removes it from all siblings. */
|
|
68
|
+
private _setActive;
|
|
69
|
+
/**
|
|
70
|
+
* For nc-a items with auto-active: set active on the item whose href
|
|
71
|
+
* matches the current pathname. Handles exact and prefix matches.
|
|
72
|
+
*/
|
|
73
|
+
private _syncActiveFromPath;
|
|
74
|
+
private _attachSearch;
|
|
75
|
+
private _filterItems;
|
|
76
|
+
}
|