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,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NcTimePicker Component
|
|
3
|
+
*
|
|
4
|
+
* Attributes:
|
|
5
|
+
* - name: string
|
|
6
|
+
* - value: string - HH:MM or HH:MM:SS (24-hour)
|
|
7
|
+
* - format: '12'|'24' (default: '24')
|
|
8
|
+
* - show-seconds: boolean - include seconds column (default: false)
|
|
9
|
+
* - min: string - minimum time HH:MM
|
|
10
|
+
* - max: string - maximum time HH:MM
|
|
11
|
+
* - step: number - minute step interval (default: 1)
|
|
12
|
+
* - placeholder: string (default: '--:--')
|
|
13
|
+
* - disabled: boolean
|
|
14
|
+
* - readonly: boolean
|
|
15
|
+
* - size: 'sm'|'md'|'lg' (default: 'md')
|
|
16
|
+
*
|
|
17
|
+
* Events:
|
|
18
|
+
* - change: CustomEvent<{ value: string; name: string }>
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* <nc-time-picker name="start" value="09:30"></nc-time-picker>
|
|
22
|
+
* <nc-time-picker name="alarm" format="12" show-seconds></nc-time-picker>
|
|
23
|
+
*/
|
|
24
|
+
import { Component, defineComponent } from '../core/component.js';
|
|
25
|
+
function pad2(n) { return String(n).padStart(2, '0'); }
|
|
26
|
+
function parseTime(v) {
|
|
27
|
+
if (!v)
|
|
28
|
+
return null;
|
|
29
|
+
const parts = v.split(':').map(Number);
|
|
30
|
+
if (parts.length < 2 || parts.some(isNaN))
|
|
31
|
+
return null;
|
|
32
|
+
return { h: parts[0] ?? 0, m: parts[1] ?? 0, s: parts[2] ?? 0 };
|
|
33
|
+
}
|
|
34
|
+
function formatValue(h, m, s, showSec) {
|
|
35
|
+
return showSec ? `${pad2(h)}:${pad2(m)}:${pad2(s)}` : `${pad2(h)}:${pad2(m)}`;
|
|
36
|
+
}
|
|
37
|
+
function to12(h) {
|
|
38
|
+
return { display: h === 0 ? 12 : h > 12 ? h - 12 : h, ampm: h < 12 ? 'AM' : 'PM' };
|
|
39
|
+
}
|
|
40
|
+
export class NcTimePicker extends Component {
|
|
41
|
+
static useShadowDOM = true;
|
|
42
|
+
static get observedAttributes() {
|
|
43
|
+
return ['name', 'value', 'format', 'show-seconds', 'min', 'max', 'step', 'placeholder', 'disabled', 'readonly', 'size'];
|
|
44
|
+
}
|
|
45
|
+
_open = false;
|
|
46
|
+
_h = 0;
|
|
47
|
+
_m = 0;
|
|
48
|
+
_s = 0;
|
|
49
|
+
_ampm = 'AM';
|
|
50
|
+
_initialized = false;
|
|
51
|
+
_outsideClick = null;
|
|
52
|
+
constructor() { super(); }
|
|
53
|
+
_initFromAttr() {
|
|
54
|
+
const t = parseTime(this.getAttribute('value'));
|
|
55
|
+
if (t) {
|
|
56
|
+
this._h = t.h;
|
|
57
|
+
this._m = t.m;
|
|
58
|
+
this._s = t.s;
|
|
59
|
+
const { ampm } = to12(t.h);
|
|
60
|
+
this._ampm = ampm;
|
|
61
|
+
}
|
|
62
|
+
this._initialized = true;
|
|
63
|
+
}
|
|
64
|
+
_is12() { return this.getAttribute('format') === '12'; }
|
|
65
|
+
_showSec() { return this.hasAttribute('show-seconds'); }
|
|
66
|
+
_displayValue() {
|
|
67
|
+
if (!this._initialized && !this.getAttribute('value'))
|
|
68
|
+
return '';
|
|
69
|
+
if (this._is12()) {
|
|
70
|
+
const { display } = to12(this._h);
|
|
71
|
+
return `${pad2(display)}:${pad2(this._m)}${this._showSec() ? `:${pad2(this._s)}` : ''} ${this._ampm}`;
|
|
72
|
+
}
|
|
73
|
+
return formatValue(this._h, this._m, this._s, this._showSec());
|
|
74
|
+
}
|
|
75
|
+
template() {
|
|
76
|
+
if (!this._initialized)
|
|
77
|
+
this._initFromAttr();
|
|
78
|
+
const disabled = this.hasAttribute('disabled');
|
|
79
|
+
const readonly = this.hasAttribute('readonly');
|
|
80
|
+
const placeholder = this.getAttribute('placeholder') || '--:--';
|
|
81
|
+
const step = Number(this.getAttribute('step') || 1);
|
|
82
|
+
const is12 = this._is12();
|
|
83
|
+
const showSec = this._showSec();
|
|
84
|
+
const displayVal = this._displayValue();
|
|
85
|
+
// Build minute options honoring step
|
|
86
|
+
const minuteOptions = Array.from({ length: Math.ceil(60 / step) }, (_, i) => i * step).filter(m => m < 60);
|
|
87
|
+
const hourRange = is12 ? 12 : 24;
|
|
88
|
+
const hourOptions = Array.from({ length: hourRange }, (_, i) => is12 ? i + 1 : i);
|
|
89
|
+
return `
|
|
90
|
+
<style>
|
|
91
|
+
:host { display: block; position: relative; font-family: var(--nc-font-family); }
|
|
92
|
+
|
|
93
|
+
.input-wrap {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
border: var(--nc-input-border);
|
|
97
|
+
border-radius: var(--nc-input-radius);
|
|
98
|
+
background: var(--nc-bg);
|
|
99
|
+
cursor: ${disabled || readonly ? 'not-allowed' : 'pointer'};
|
|
100
|
+
opacity: ${disabled ? '0.5' : '1'};
|
|
101
|
+
transition: border-color var(--nc-transition-fast), box-shadow var(--nc-transition-fast);
|
|
102
|
+
user-select: none;
|
|
103
|
+
}
|
|
104
|
+
.input-wrap:focus-within { border-color: var(--nc-input-focus-border); box-shadow: 0 0 0 3px rgba(16,185,129,.15); }
|
|
105
|
+
|
|
106
|
+
.display {
|
|
107
|
+
flex: 1;
|
|
108
|
+
padding: var(--nc-spacing-sm) var(--nc-spacing-md);
|
|
109
|
+
font-size: var(--nc-font-size-base);
|
|
110
|
+
color: ${displayVal ? 'var(--nc-text)' : 'var(--nc-text-muted)'};
|
|
111
|
+
font-variant-numeric: tabular-nums;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
:host([size="sm"]) .display { font-size: var(--nc-font-size-sm); padding: var(--nc-spacing-xs) var(--nc-spacing-sm); }
|
|
115
|
+
:host([size="lg"]) .display { font-size: var(--nc-font-size-lg); padding: var(--nc-spacing-md); }
|
|
116
|
+
|
|
117
|
+
.input-icon {
|
|
118
|
+
padding: 0 var(--nc-spacing-sm);
|
|
119
|
+
color: var(--nc-text-muted);
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-shrink: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.panel {
|
|
125
|
+
position: absolute;
|
|
126
|
+
top: calc(100% + 6px);
|
|
127
|
+
left: 0;
|
|
128
|
+
z-index: 500;
|
|
129
|
+
background: var(--nc-bg);
|
|
130
|
+
border: 1px solid var(--nc-border);
|
|
131
|
+
border-radius: var(--nc-radius-md, 8px);
|
|
132
|
+
box-shadow: var(--nc-shadow-lg);
|
|
133
|
+
display: ${this._open ? 'flex' : 'none'};
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
min-width: 240px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.columns {
|
|
140
|
+
display: flex;
|
|
141
|
+
gap: 0;
|
|
142
|
+
max-height: 220px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.col {
|
|
146
|
+
flex: 1;
|
|
147
|
+
overflow-y: auto;
|
|
148
|
+
scroll-snap-type: y mandatory;
|
|
149
|
+
border-right: 1px solid var(--nc-border);
|
|
150
|
+
scrollbar-width: thin;
|
|
151
|
+
}
|
|
152
|
+
.col:last-child { border-right: none; }
|
|
153
|
+
|
|
154
|
+
.col-header {
|
|
155
|
+
position: sticky;
|
|
156
|
+
top: 0;
|
|
157
|
+
background: var(--nc-bg-secondary);
|
|
158
|
+
font-size: var(--nc-font-size-xs);
|
|
159
|
+
font-weight: var(--nc-font-weight-semibold);
|
|
160
|
+
color: var(--nc-text-muted);
|
|
161
|
+
text-transform: uppercase;
|
|
162
|
+
letter-spacing: 0.05em;
|
|
163
|
+
padding: 5px var(--nc-spacing-sm);
|
|
164
|
+
text-align: center;
|
|
165
|
+
z-index: 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.col-item {
|
|
169
|
+
padding: 7px var(--nc-spacing-sm);
|
|
170
|
+
text-align: center;
|
|
171
|
+
font-size: var(--nc-font-size-sm);
|
|
172
|
+
font-variant-numeric: tabular-nums;
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
color: var(--nc-text);
|
|
175
|
+
scroll-snap-align: start;
|
|
176
|
+
transition: background var(--nc-transition-fast);
|
|
177
|
+
}
|
|
178
|
+
.col-item:hover { background: var(--nc-bg-secondary); }
|
|
179
|
+
.col-item.selected { background: var(--nc-primary); color: #fff; font-weight: var(--nc-font-weight-semibold); }
|
|
180
|
+
.col-item:disabled, .col-item[disabled] { opacity: 0.3; pointer-events: none; }
|
|
181
|
+
|
|
182
|
+
.panel-footer {
|
|
183
|
+
display: flex;
|
|
184
|
+
justify-content: space-between;
|
|
185
|
+
gap: var(--nc-spacing-xs);
|
|
186
|
+
padding: var(--nc-spacing-sm);
|
|
187
|
+
border-top: 1px solid var(--nc-border);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.panel-btn {
|
|
191
|
+
flex: 1;
|
|
192
|
+
padding: 5px;
|
|
193
|
+
border-radius: var(--nc-radius-sm, 4px);
|
|
194
|
+
font-size: var(--nc-font-size-xs);
|
|
195
|
+
font-family: var(--nc-font-family);
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
border: 1px solid var(--nc-border);
|
|
198
|
+
background: var(--nc-bg-secondary);
|
|
199
|
+
color: var(--nc-text);
|
|
200
|
+
transition: background var(--nc-transition-fast);
|
|
201
|
+
}
|
|
202
|
+
.panel-btn:hover { background: var(--nc-bg-tertiary); }
|
|
203
|
+
.panel-btn--primary { background: var(--nc-primary); color: #fff; border-color: var(--nc-primary); }
|
|
204
|
+
.panel-btn--primary:hover { opacity: 0.9; }
|
|
205
|
+
</style>
|
|
206
|
+
|
|
207
|
+
<div class="input-wrap" tabindex="${disabled ? '-1' : '0'}" role="button" aria-haspopup="dialog" aria-expanded="${this._open}">
|
|
208
|
+
<span class="display">${displayVal || placeholder}</span>
|
|
209
|
+
<span class="input-icon">
|
|
210
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" width="14" height="14">
|
|
211
|
+
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.2"/>
|
|
212
|
+
<path d="M8 5v3.5l2 1.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
|
213
|
+
</svg>
|
|
214
|
+
</span>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div class="panel" role="dialog">
|
|
218
|
+
<div class="columns">
|
|
219
|
+
<!-- Hours -->
|
|
220
|
+
<div class="col" data-col="hour">
|
|
221
|
+
<div class="col-header">HH</div>
|
|
222
|
+
${hourOptions.map(h => {
|
|
223
|
+
const isSelected = is12 ? to12(this._h).display === h : this._h === h;
|
|
224
|
+
return `<div class="col-item${isSelected ? ' selected' : ''}" data-col="hour" data-value="${h}">${pad2(h)}</div>`;
|
|
225
|
+
}).join('')}
|
|
226
|
+
</div>
|
|
227
|
+
<!-- Minutes -->
|
|
228
|
+
<div class="col" data-col="minute">
|
|
229
|
+
<div class="col-header">MM</div>
|
|
230
|
+
${minuteOptions.map(m => {
|
|
231
|
+
const isSelected = this._m === m;
|
|
232
|
+
return `<div class="col-item${isSelected ? ' selected' : ''}" data-col="minute" data-value="${m}">${pad2(m)}</div>`;
|
|
233
|
+
}).join('')}
|
|
234
|
+
</div>
|
|
235
|
+
${showSec ? `
|
|
236
|
+
<!-- Seconds -->
|
|
237
|
+
<div class="col" data-col="second">
|
|
238
|
+
<div class="col-header">SS</div>
|
|
239
|
+
${Array.from({ length: 60 }, (_, i) => i).map(s => {
|
|
240
|
+
const isSelected = this._s === s;
|
|
241
|
+
return `<div class="col-item${isSelected ? ' selected' : ''}" data-col="second" data-value="${s}">${pad2(s)}</div>`;
|
|
242
|
+
}).join('')}
|
|
243
|
+
</div>` : ''}
|
|
244
|
+
${is12 ? `
|
|
245
|
+
<!-- AM/PM -->
|
|
246
|
+
<div class="col" data-col="ampm">
|
|
247
|
+
<div class="col-header">AM/PM</div>
|
|
248
|
+
<div class="col-item${this._ampm === 'AM' ? ' selected' : ''}" data-col="ampm" data-value="AM">AM</div>
|
|
249
|
+
<div class="col-item${this._ampm === 'PM' ? ' selected' : ''}" data-col="ampm" data-value="PM">PM</div>
|
|
250
|
+
</div>` : ''}
|
|
251
|
+
</div>
|
|
252
|
+
<div class="panel-footer">
|
|
253
|
+
<button class="panel-btn" data-action="clear" type="button">Clear</button>
|
|
254
|
+
<button class="panel-btn" data-action="now" type="button">Now</button>
|
|
255
|
+
<button class="panel-btn panel-btn--primary" data-action="apply" type="button">Apply</button>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<input type="hidden" name="${this.getAttribute('name') || ''}" value="${this._initialized ? formatValue(this._h, this._m, this._s, showSec) : ''}" />
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
onMount() {
|
|
263
|
+
this._bindEvents();
|
|
264
|
+
this._scrollToSelected();
|
|
265
|
+
}
|
|
266
|
+
_bindEvents() {
|
|
267
|
+
const wrap = this.$('.input-wrap');
|
|
268
|
+
const panel = this.$('.panel');
|
|
269
|
+
wrap.addEventListener('click', () => {
|
|
270
|
+
if (this.hasAttribute('disabled') || this.hasAttribute('readonly'))
|
|
271
|
+
return;
|
|
272
|
+
this._open = !this._open;
|
|
273
|
+
panel.style.display = this._open ? 'flex' : 'none';
|
|
274
|
+
wrap.setAttribute('aria-expanded', String(this._open));
|
|
275
|
+
if (this._open)
|
|
276
|
+
this._scrollToSelected();
|
|
277
|
+
});
|
|
278
|
+
wrap.addEventListener('keydown', (e) => {
|
|
279
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
280
|
+
e.preventDefault();
|
|
281
|
+
wrap.click();
|
|
282
|
+
}
|
|
283
|
+
if (e.key === 'Escape') {
|
|
284
|
+
this._open = false;
|
|
285
|
+
panel.style.display = 'none';
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
// Column item clicks
|
|
289
|
+
panel.addEventListener('click', (e) => {
|
|
290
|
+
const item = e.target.closest('[data-col][data-value]');
|
|
291
|
+
const action = e.target.closest('[data-action]');
|
|
292
|
+
if (item) {
|
|
293
|
+
const col = item.dataset.col;
|
|
294
|
+
const val = item.dataset.value;
|
|
295
|
+
this._handleColSelect(col, val);
|
|
296
|
+
// Update active highlight in column
|
|
297
|
+
const colEl = panel.querySelector(`[data-col="${col}"].col`);
|
|
298
|
+
colEl?.querySelectorAll('.col-item').forEach(el => {
|
|
299
|
+
el.classList.toggle('selected', el.dataset.value === val);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (action) {
|
|
303
|
+
switch (action.dataset.action) {
|
|
304
|
+
case 'clear':
|
|
305
|
+
this._clear();
|
|
306
|
+
break;
|
|
307
|
+
case 'now':
|
|
308
|
+
this._setNow();
|
|
309
|
+
break;
|
|
310
|
+
case 'apply':
|
|
311
|
+
this._apply();
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
// Outside click
|
|
317
|
+
this._outsideClick = (e) => {
|
|
318
|
+
if (!this.contains(e.target) && !this.shadowRoot.contains(e.target)) {
|
|
319
|
+
this._open = false;
|
|
320
|
+
if (panel)
|
|
321
|
+
panel.style.display = 'none';
|
|
322
|
+
if (wrap)
|
|
323
|
+
wrap.setAttribute('aria-expanded', 'false');
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
document.addEventListener('mousedown', this._outsideClick);
|
|
327
|
+
}
|
|
328
|
+
_handleColSelect(col, val) {
|
|
329
|
+
switch (col) {
|
|
330
|
+
case 'hour': {
|
|
331
|
+
const n = Number(val);
|
|
332
|
+
if (this._is12()) {
|
|
333
|
+
this._h = this._ampm === 'AM'
|
|
334
|
+
? (n === 12 ? 0 : n)
|
|
335
|
+
: (n === 12 ? 12 : n + 12);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
this._h = n;
|
|
339
|
+
}
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case 'minute':
|
|
343
|
+
this._m = Number(val);
|
|
344
|
+
break;
|
|
345
|
+
case 'second':
|
|
346
|
+
this._s = Number(val);
|
|
347
|
+
break;
|
|
348
|
+
case 'ampm': {
|
|
349
|
+
this._ampm = val;
|
|
350
|
+
if (val === 'AM' && this._h >= 12)
|
|
351
|
+
this._h -= 12;
|
|
352
|
+
if (val === 'PM' && this._h < 12)
|
|
353
|
+
this._h += 12;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
this._updateDisplay();
|
|
358
|
+
}
|
|
359
|
+
_updateDisplay() {
|
|
360
|
+
const wrap = this.$('.display');
|
|
361
|
+
const hidden = this.$('input[type="hidden"]');
|
|
362
|
+
const val = this._displayValue();
|
|
363
|
+
if (wrap) {
|
|
364
|
+
wrap.textContent = val || (this.getAttribute('placeholder') || '--:--');
|
|
365
|
+
wrap.style.color = val ? 'var(--nc-text)' : 'var(--nc-text-muted)';
|
|
366
|
+
}
|
|
367
|
+
if (hidden)
|
|
368
|
+
hidden.value = formatValue(this._h, this._m, this._s, this._showSec());
|
|
369
|
+
}
|
|
370
|
+
_apply() {
|
|
371
|
+
this._initialized = true;
|
|
372
|
+
const value = formatValue(this._h, this._m, this._s, this._showSec());
|
|
373
|
+
this.setAttribute('value', value);
|
|
374
|
+
this._open = false;
|
|
375
|
+
const panel = this.$('.panel');
|
|
376
|
+
const wrap = this.$('.input-wrap');
|
|
377
|
+
if (panel)
|
|
378
|
+
panel.style.display = 'none';
|
|
379
|
+
if (wrap)
|
|
380
|
+
wrap.setAttribute('aria-expanded', 'false');
|
|
381
|
+
this._updateDisplay();
|
|
382
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
383
|
+
bubbles: true, composed: true,
|
|
384
|
+
detail: { value, name: this.getAttribute('name') || '' }
|
|
385
|
+
}));
|
|
386
|
+
}
|
|
387
|
+
_clear() {
|
|
388
|
+
this._h = 0;
|
|
389
|
+
this._m = 0;
|
|
390
|
+
this._s = 0;
|
|
391
|
+
this._ampm = 'AM';
|
|
392
|
+
this._initialized = false;
|
|
393
|
+
this.removeAttribute('value');
|
|
394
|
+
this._open = false;
|
|
395
|
+
const panel = this.$('.panel');
|
|
396
|
+
if (panel)
|
|
397
|
+
panel.style.display = 'none';
|
|
398
|
+
this._updateDisplay();
|
|
399
|
+
const wrap = this.$('.display');
|
|
400
|
+
if (wrap)
|
|
401
|
+
wrap.style.color = 'var(--nc-text-muted)';
|
|
402
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
403
|
+
bubbles: true, composed: true,
|
|
404
|
+
detail: { value: '', name: this.getAttribute('name') || '' }
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
407
|
+
_setNow() {
|
|
408
|
+
const now = new Date();
|
|
409
|
+
this._h = now.getHours();
|
|
410
|
+
this._m = now.getMinutes();
|
|
411
|
+
this._s = now.getSeconds();
|
|
412
|
+
this._ampm = this._h < 12 ? 'AM' : 'PM';
|
|
413
|
+
this._initialized = true;
|
|
414
|
+
this.render();
|
|
415
|
+
this._bindEvents();
|
|
416
|
+
this._scrollToSelected();
|
|
417
|
+
}
|
|
418
|
+
_scrollToSelected() {
|
|
419
|
+
requestAnimationFrame(() => {
|
|
420
|
+
this.shadowRoot.querySelectorAll('.col').forEach(col => {
|
|
421
|
+
const selected = col.querySelector('.selected');
|
|
422
|
+
if (selected)
|
|
423
|
+
selected.scrollIntoView({ block: 'nearest' });
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
onUnmount() {
|
|
428
|
+
if (this._outsideClick)
|
|
429
|
+
document.removeEventListener('mousedown', this._outsideClick);
|
|
430
|
+
}
|
|
431
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
432
|
+
if (oldValue === newValue)
|
|
433
|
+
return;
|
|
434
|
+
if (name === 'value' && this._mounted) {
|
|
435
|
+
const t = parseTime(newValue);
|
|
436
|
+
if (t) {
|
|
437
|
+
this._h = t.h;
|
|
438
|
+
this._m = t.m;
|
|
439
|
+
this._s = t.s;
|
|
440
|
+
this._ampm = to12(t.h).ampm;
|
|
441
|
+
this._initialized = true;
|
|
442
|
+
}
|
|
443
|
+
this._updateDisplay();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (this._mounted) {
|
|
447
|
+
this.render();
|
|
448
|
+
this._bindEvents();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
defineComponent('nc-time-picker', NcTimePicker);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NcTimeline Component - vertical event timeline
|
|
3
|
+
*
|
|
4
|
+
* Attributes:
|
|
5
|
+
* align - 'left'(default)|'right'|'alternate' - which side the content appears
|
|
6
|
+
* size - 'sm'|'md'(default)|'lg' - dot and line scale
|
|
7
|
+
* dense - boolean - reduce spacing
|
|
8
|
+
*
|
|
9
|
+
* Slots: nc-timeline-item elements
|
|
10
|
+
*
|
|
11
|
+
* ---
|
|
12
|
+
*
|
|
13
|
+
* NcTimelineItem Component - single timeline event
|
|
14
|
+
*
|
|
15
|
+
* Attributes:
|
|
16
|
+
* color - dot color preset: 'primary'(default)|'success'|'warning'|'danger'|'neutral'
|
|
17
|
+
* OR any valid CSS color string
|
|
18
|
+
* icon - small icon inside the dot (same icon names as nc-nav-item)
|
|
19
|
+
* title - event heading
|
|
20
|
+
* time - timestamp / relative time string (shown muted)
|
|
21
|
+
* status - 'completed'|'active'|'pending'|'error' (sets color automatically)
|
|
22
|
+
* no-line - boolean - hide the connector line (usually set on last item)
|
|
23
|
+
*
|
|
24
|
+
* Slots:
|
|
25
|
+
* icon - custom dot content
|
|
26
|
+
* title - override title
|
|
27
|
+
* time - time content
|
|
28
|
+
* (default)- event body / description
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* <nc-timeline>
|
|
32
|
+
* <nc-timeline-item title="Order placed" time="2h ago" status="completed">
|
|
33
|
+
* Your order #4521 was received.
|
|
34
|
+
* </nc-timeline-item>
|
|
35
|
+
* <nc-timeline-item title="Processing" status="active">
|
|
36
|
+
* We're preparing your items.
|
|
37
|
+
* </nc-timeline-item>
|
|
38
|
+
* <nc-timeline-item title="Delivery" status="pending" no-line>
|
|
39
|
+
* Estimated 2-3 business days.
|
|
40
|
+
* </nc-timeline-item>
|
|
41
|
+
* </nc-timeline>
|
|
42
|
+
*/
|
|
43
|
+
import { Component } from '../core/component.js';
|
|
44
|
+
export declare class NcTimelineItem extends Component {
|
|
45
|
+
static useShadowDOM: boolean;
|
|
46
|
+
static get observedAttributes(): string[];
|
|
47
|
+
template(): string;
|
|
48
|
+
attributeChangedCallback(n: string, o: string, v: string): void;
|
|
49
|
+
}
|
|
50
|
+
export declare class NcTimeline extends Component {
|
|
51
|
+
static useShadowDOM: boolean;
|
|
52
|
+
template(): string;
|
|
53
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NcTimeline Component - vertical event timeline
|
|
3
|
+
*
|
|
4
|
+
* Attributes:
|
|
5
|
+
* align - 'left'(default)|'right'|'alternate' - which side the content appears
|
|
6
|
+
* size - 'sm'|'md'(default)|'lg' - dot and line scale
|
|
7
|
+
* dense - boolean - reduce spacing
|
|
8
|
+
*
|
|
9
|
+
* Slots: nc-timeline-item elements
|
|
10
|
+
*
|
|
11
|
+
* ---
|
|
12
|
+
*
|
|
13
|
+
* NcTimelineItem Component - single timeline event
|
|
14
|
+
*
|
|
15
|
+
* Attributes:
|
|
16
|
+
* color - dot color preset: 'primary'(default)|'success'|'warning'|'danger'|'neutral'
|
|
17
|
+
* OR any valid CSS color string
|
|
18
|
+
* icon - small icon inside the dot (same icon names as nc-nav-item)
|
|
19
|
+
* title - event heading
|
|
20
|
+
* time - timestamp / relative time string (shown muted)
|
|
21
|
+
* status - 'completed'|'active'|'pending'|'error' (sets color automatically)
|
|
22
|
+
* no-line - boolean - hide the connector line (usually set on last item)
|
|
23
|
+
*
|
|
24
|
+
* Slots:
|
|
25
|
+
* icon - custom dot content
|
|
26
|
+
* title - override title
|
|
27
|
+
* time - time content
|
|
28
|
+
* (default)- event body / description
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* <nc-timeline>
|
|
32
|
+
* <nc-timeline-item title="Order placed" time="2h ago" status="completed">
|
|
33
|
+
* Your order #4521 was received.
|
|
34
|
+
* </nc-timeline-item>
|
|
35
|
+
* <nc-timeline-item title="Processing" status="active">
|
|
36
|
+
* We're preparing your items.
|
|
37
|
+
* </nc-timeline-item>
|
|
38
|
+
* <nc-timeline-item title="Delivery" status="pending" no-line>
|
|
39
|
+
* Estimated 2-3 business days.
|
|
40
|
+
* </nc-timeline-item>
|
|
41
|
+
* </nc-timeline>
|
|
42
|
+
*/
|
|
43
|
+
import { Component, defineComponent } from '../core/component.js';
|
|
44
|
+
const STATUS_COLORS = {
|
|
45
|
+
completed: 'var(--nc-success)',
|
|
46
|
+
active: 'var(--nc-primary)',
|
|
47
|
+
pending: 'var(--nc-text-muted)',
|
|
48
|
+
error: 'var(--nc-danger)',
|
|
49
|
+
};
|
|
50
|
+
const SMALL_ICONS = {
|
|
51
|
+
check: `<polyline points="20 6 9 17 4 12"/>`,
|
|
52
|
+
x: `<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>`,
|
|
53
|
+
star: `<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>`,
|
|
54
|
+
info: `<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>`,
|
|
55
|
+
alert: `<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>`,
|
|
56
|
+
};
|
|
57
|
+
const iconSvg = (p, sz = 10) => `<svg xmlns="http://www.w3.org/2000/svg" width="${sz}" height="${sz}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">${p}</svg>`;
|
|
58
|
+
// ── NcTimelineItem ────────────────────────────────────────────────────────────
|
|
59
|
+
export class NcTimelineItem extends Component {
|
|
60
|
+
static useShadowDOM = true;
|
|
61
|
+
static get observedAttributes() { return ['color', 'status', 'active', 'no-line']; }
|
|
62
|
+
template() {
|
|
63
|
+
const title = this.getAttribute('title') ?? '';
|
|
64
|
+
const time = this.getAttribute('time') ?? '';
|
|
65
|
+
const status = this.getAttribute('status') ?? '';
|
|
66
|
+
const iconKey = this.getAttribute('icon') ?? '';
|
|
67
|
+
const noLine = this.hasAttribute('no-line');
|
|
68
|
+
const color = this.getAttribute('color')
|
|
69
|
+
?? STATUS_COLORS[status]
|
|
70
|
+
?? 'var(--nc-primary)';
|
|
71
|
+
const dotContent = SMALL_ICONS[iconKey]
|
|
72
|
+
? iconSvg(SMALL_ICONS[iconKey], 10)
|
|
73
|
+
: (status === 'completed' ? iconSvg(SMALL_ICONS.check, 10) : '');
|
|
74
|
+
const isActive = status === 'active';
|
|
75
|
+
const dotSz = isActive ? 14 : 12;
|
|
76
|
+
return `
|
|
77
|
+
<style>
|
|
78
|
+
:host { display: flex; font-family: var(--nc-font-family); }
|
|
79
|
+
.col-dot {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
align-items: center;
|
|
83
|
+
flex-shrink: 0;
|
|
84
|
+
width: 28px;
|
|
85
|
+
margin-right: var(--nc-spacing-md);
|
|
86
|
+
}
|
|
87
|
+
.dot {
|
|
88
|
+
width: ${dotSz}px;
|
|
89
|
+
height: ${dotSz}px;
|
|
90
|
+
border-radius: 50%;
|
|
91
|
+
background: ${color};
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
flex-shrink: 0;
|
|
96
|
+
color: #fff;
|
|
97
|
+
${isActive ? `box-shadow: 0 0 0 4px rgba(0,0,0,.1), 0 0 0 3px ${color}33;` : ''}
|
|
98
|
+
margin-top: 4px;
|
|
99
|
+
}
|
|
100
|
+
.line {
|
|
101
|
+
flex: 1;
|
|
102
|
+
width: 2px;
|
|
103
|
+
background: var(--nc-border);
|
|
104
|
+
margin-top: 4px;
|
|
105
|
+
min-height: 24px;
|
|
106
|
+
display: ${noLine ? 'none' : 'block'};
|
|
107
|
+
}
|
|
108
|
+
.content {
|
|
109
|
+
flex: 1;
|
|
110
|
+
padding-bottom: ${noLine ? '0' : 'var(--nc-spacing-lg)'};
|
|
111
|
+
min-width: 0;
|
|
112
|
+
}
|
|
113
|
+
.header {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: baseline;
|
|
116
|
+
justify-content: space-between;
|
|
117
|
+
gap: var(--nc-spacing-sm);
|
|
118
|
+
margin-bottom: 4px;
|
|
119
|
+
}
|
|
120
|
+
.title {
|
|
121
|
+
font-size: var(--nc-font-size-sm);
|
|
122
|
+
font-weight: var(--nc-font-weight-semibold);
|
|
123
|
+
color: var(--nc-text);
|
|
124
|
+
margin: 0;
|
|
125
|
+
}
|
|
126
|
+
.time {
|
|
127
|
+
font-size: var(--nc-font-size-xs);
|
|
128
|
+
color: var(--nc-text-muted);
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
}
|
|
132
|
+
.body {
|
|
133
|
+
font-size: var(--nc-font-size-sm);
|
|
134
|
+
color: var(--nc-text-secondary);
|
|
135
|
+
line-height: var(--nc-line-height-relaxed, 1.65);
|
|
136
|
+
}
|
|
137
|
+
</style>
|
|
138
|
+
<div class="col-dot">
|
|
139
|
+
<div class="dot">
|
|
140
|
+
<slot name="icon">${dotContent}</slot>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="line"></div>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="content">
|
|
145
|
+
<div class="header">
|
|
146
|
+
<p class="title"><slot name="title">${title}</slot></p>
|
|
147
|
+
${time ? `<span class="time"><slot name="time">${time}</slot></span>` : '<slot name="time"></slot>'}
|
|
148
|
+
</div>
|
|
149
|
+
<div class="body"><slot></slot></div>
|
|
150
|
+
</div>
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
attributeChangedCallback(n, o, v) {
|
|
154
|
+
if (o !== v && this._mounted)
|
|
155
|
+
this.render();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
defineComponent('nc-timeline-item', NcTimelineItem);
|
|
159
|
+
// ── NcTimeline ────────────────────────────────────────────────────────────────
|
|
160
|
+
export class NcTimeline extends Component {
|
|
161
|
+
static useShadowDOM = true;
|
|
162
|
+
template() {
|
|
163
|
+
return `
|
|
164
|
+
<style>
|
|
165
|
+
:host { display: block; padding: var(--nc-spacing-sm) 0; }
|
|
166
|
+
</style>
|
|
167
|
+
<slot></slot>
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
defineComponent('nc-timeline', NcTimeline);
|