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.
Files changed (145) hide show
  1. package/README.md +22 -0
  2. package/dist/components/builtinRegistry.d.ts +2 -0
  3. package/dist/components/builtinRegistry.js +72 -0
  4. package/dist/components/index.d.ts +59 -0
  5. package/dist/components/index.js +59 -0
  6. package/dist/components/loading-spinner.d.ts +5 -0
  7. package/dist/components/loading-spinner.js +48 -0
  8. package/dist/components/nc-a.d.ts +45 -0
  9. package/dist/components/nc-a.js +290 -0
  10. package/dist/components/nc-accordion.d.ts +36 -0
  11. package/dist/components/nc-accordion.js +186 -0
  12. package/dist/components/nc-alert.d.ts +11 -0
  13. package/dist/components/nc-alert.js +127 -0
  14. package/dist/components/nc-animation.d.ts +117 -0
  15. package/dist/components/nc-animation.js +1053 -0
  16. package/dist/components/nc-autocomplete.d.ts +41 -0
  17. package/dist/components/nc-autocomplete.js +275 -0
  18. package/dist/components/nc-avatar-group.d.ts +7 -0
  19. package/dist/components/nc-avatar-group.js +85 -0
  20. package/dist/components/nc-avatar.d.ts +9 -0
  21. package/dist/components/nc-avatar.js +127 -0
  22. package/dist/components/nc-badge.d.ts +7 -0
  23. package/dist/components/nc-badge.js +63 -0
  24. package/dist/components/nc-bottom-nav.d.ts +53 -0
  25. package/dist/components/nc-bottom-nav.js +198 -0
  26. package/dist/components/nc-breadcrumb.d.ts +10 -0
  27. package/dist/components/nc-breadcrumb.js +71 -0
  28. package/dist/components/nc-button.d.ts +38 -0
  29. package/dist/components/nc-button.js +293 -0
  30. package/dist/components/nc-card.d.ts +11 -0
  31. package/dist/components/nc-card.js +74 -0
  32. package/dist/components/nc-checkbox.d.ts +16 -0
  33. package/dist/components/nc-checkbox.js +194 -0
  34. package/dist/components/nc-chip.d.ts +8 -0
  35. package/dist/components/nc-chip.js +89 -0
  36. package/dist/components/nc-code.d.ts +37 -0
  37. package/dist/components/nc-code.js +315 -0
  38. package/dist/components/nc-collapsible.d.ts +33 -0
  39. package/dist/components/nc-collapsible.js +148 -0
  40. package/dist/components/nc-color-picker.d.ts +33 -0
  41. package/dist/components/nc-color-picker.js +265 -0
  42. package/dist/components/nc-copy-button.d.ts +10 -0
  43. package/dist/components/nc-copy-button.js +94 -0
  44. package/dist/components/nc-date-picker.d.ts +41 -0
  45. package/dist/components/nc-date-picker.js +443 -0
  46. package/dist/components/nc-div.d.ts +53 -0
  47. package/dist/components/nc-div.js +270 -0
  48. package/dist/components/nc-divider.d.ts +7 -0
  49. package/dist/components/nc-divider.js +57 -0
  50. package/dist/components/nc-drawer.d.ts +40 -0
  51. package/dist/components/nc-drawer.js +217 -0
  52. package/dist/components/nc-dropdown.d.ts +41 -0
  53. package/dist/components/nc-dropdown.js +170 -0
  54. package/dist/components/nc-empty-state.d.ts +5 -0
  55. package/dist/components/nc-empty-state.js +76 -0
  56. package/dist/components/nc-file-upload.d.ts +40 -0
  57. package/dist/components/nc-file-upload.js +336 -0
  58. package/dist/components/nc-form.d.ts +70 -0
  59. package/dist/components/nc-form.js +273 -0
  60. package/dist/components/nc-image.d.ts +10 -0
  61. package/dist/components/nc-image.js +139 -0
  62. package/dist/components/nc-input.d.ts +25 -0
  63. package/dist/components/nc-input.js +302 -0
  64. package/dist/components/nc-kbd.d.ts +5 -0
  65. package/dist/components/nc-kbd.js +34 -0
  66. package/dist/components/nc-menu-item.d.ts +43 -0
  67. package/dist/components/nc-menu-item.js +182 -0
  68. package/dist/components/nc-menu.d.ts +76 -0
  69. package/dist/components/nc-menu.js +360 -0
  70. package/dist/components/nc-modal.d.ts +51 -0
  71. package/dist/components/nc-modal.js +231 -0
  72. package/dist/components/nc-nav-item.d.ts +35 -0
  73. package/dist/components/nc-nav-item.js +142 -0
  74. package/dist/components/nc-number-input.d.ts +22 -0
  75. package/dist/components/nc-number-input.js +270 -0
  76. package/dist/components/nc-otp-input.d.ts +41 -0
  77. package/dist/components/nc-otp-input.js +227 -0
  78. package/dist/components/nc-pagination.d.ts +28 -0
  79. package/dist/components/nc-pagination.js +171 -0
  80. package/dist/components/nc-popover.d.ts +58 -0
  81. package/dist/components/nc-popover.js +301 -0
  82. package/dist/components/nc-progress-circular.d.ts +7 -0
  83. package/dist/components/nc-progress-circular.js +67 -0
  84. package/dist/components/nc-progress.d.ts +7 -0
  85. package/dist/components/nc-progress.js +109 -0
  86. package/dist/components/nc-radio.d.ts +13 -0
  87. package/dist/components/nc-radio.js +169 -0
  88. package/dist/components/nc-rating.d.ts +19 -0
  89. package/dist/components/nc-rating.js +187 -0
  90. package/dist/components/nc-rich-text.d.ts +43 -0
  91. package/dist/components/nc-rich-text.js +310 -0
  92. package/dist/components/nc-scroll-top.d.ts +28 -0
  93. package/dist/components/nc-scroll-top.js +103 -0
  94. package/dist/components/nc-select.d.ts +51 -0
  95. package/dist/components/nc-select.js +425 -0
  96. package/dist/components/nc-skeleton.d.ts +7 -0
  97. package/dist/components/nc-skeleton.js +90 -0
  98. package/dist/components/nc-slider.d.ts +41 -0
  99. package/dist/components/nc-slider.js +268 -0
  100. package/dist/components/nc-snackbar.d.ts +51 -0
  101. package/dist/components/nc-snackbar.js +200 -0
  102. package/dist/components/nc-splash.d.ts +25 -0
  103. package/dist/components/nc-splash.js +296 -0
  104. package/dist/components/nc-stepper.d.ts +50 -0
  105. package/dist/components/nc-stepper.js +236 -0
  106. package/dist/components/nc-switch.d.ts +14 -0
  107. package/dist/components/nc-switch.js +194 -0
  108. package/dist/components/nc-tab-item.d.ts +39 -0
  109. package/dist/components/nc-tab-item.js +127 -0
  110. package/dist/components/nc-table.d.ts +44 -0
  111. package/dist/components/nc-table.js +265 -0
  112. package/dist/components/nc-tabs.d.ts +79 -0
  113. package/dist/components/nc-tabs.js +519 -0
  114. package/dist/components/nc-tag-input.d.ts +49 -0
  115. package/dist/components/nc-tag-input.js +268 -0
  116. package/dist/components/nc-textarea.d.ts +15 -0
  117. package/dist/components/nc-textarea.js +164 -0
  118. package/dist/components/nc-time-picker.d.ts +51 -0
  119. package/dist/components/nc-time-picker.js +452 -0
  120. package/dist/components/nc-timeline.d.ts +53 -0
  121. package/dist/components/nc-timeline.js +171 -0
  122. package/dist/components/nc-tooltip.d.ts +27 -0
  123. package/dist/components/nc-tooltip.js +135 -0
  124. package/dist/core/component.d.ts +33 -0
  125. package/dist/core/component.js +208 -0
  126. package/dist/core/gpu-animation.d.ts +141 -0
  127. package/dist/core/gpu-animation.js +474 -0
  128. package/dist/core/lazyComponents.d.ts +13 -0
  129. package/dist/core/lazyComponents.js +73 -0
  130. package/dist/core/router.d.ts +55 -0
  131. package/dist/core/router.js +424 -0
  132. package/dist/core/state.d.ts +18 -0
  133. package/dist/core/state.js +153 -0
  134. package/dist/index.d.ts +14 -0
  135. package/dist/index.js +11 -0
  136. package/dist/utils/cacheBuster.d.ts +9 -0
  137. package/dist/utils/cacheBuster.js +12 -0
  138. package/dist/utils/dom.d.ts +16 -0
  139. package/dist/utils/dom.js +70 -0
  140. package/dist/utils/events.d.ts +20 -0
  141. package/dist/utils/events.js +80 -0
  142. package/dist/utils/templates.d.ts +2 -0
  143. package/dist/utils/templates.js +2 -0
  144. package/package.json +53 -0
  145. package/src/styles/base.css +40 -0
@@ -0,0 +1,142 @@
1
+ /**
2
+ * NcNavItem Component - sidebar / navigation link with icon, label, badge, active state
3
+ *
4
+ * Attributes:
5
+ * href - link URL; if omitted renders as a <button>
6
+ * label - visible text (can also use default slot)
7
+ * icon - preset icon name OR raw SVG string (see below)
8
+ * active - boolean - mark as active
9
+ * disabled - boolean
10
+ * badge - badge count (number string); shown as a pill on the right
11
+ * badge-variant - 'primary'(default)|'success'|'danger'|'warning'
12
+ * indent - indentation level for nested nav (default: 0)
13
+ * target - anchor target (default: '_self')
14
+ * exact - boolean - only activate on exact URL match (router integration)
15
+ *
16
+ * Slots:
17
+ * icon - custom icon (overrides icon attribute)
18
+ * (default) - label text (overrides label attribute)
19
+ * badge - custom badge content
20
+ *
21
+ * Events:
22
+ * nav-click - CustomEvent<{ href: string | null }> - bubbles from both <a> and <button>
23
+ *
24
+ * Usage:
25
+ * <nc-nav-item href="/dashboard" label="Dashboard" icon="home" active></nc-nav-item>
26
+ * <nc-nav-item href="/users" label="Users" icon="users" badge="14"></nc-nav-item>
27
+ */
28
+ import { Component, defineComponent } from '../core/component.js';
29
+ const NAV_ICONS = {
30
+ home: `<path d="M3 9.5L12 3l9 6.5V20a1 1 0 0 1-1 1H14v-5h-4v5H4a1 1 0 0 1-1-1V9.5z"/>`,
31
+ dashboard: `<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>`,
32
+ users: `<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>`,
33
+ settings: `<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>`,
34
+ chart: `<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>`,
35
+ inbox: `<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>`,
36
+ file: `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>`,
37
+ folder: `<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>`,
38
+ bell: `<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>`,
39
+ lock: `<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>`,
40
+ logout: `<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>`,
41
+ help: `<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/>`,
42
+ };
43
+ const svgWrap = (paths) => `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
44
+ export class NcNavItem extends Component {
45
+ static useShadowDOM = true;
46
+ static get observedAttributes() { return ['active', 'disabled', 'badge', 'href']; }
47
+ template() {
48
+ const href = this.getAttribute('href');
49
+ const label = this.getAttribute('label') ?? '';
50
+ const iconName = this.getAttribute('icon') ?? '';
51
+ const active = this.hasAttribute('active');
52
+ const disabled = this.hasAttribute('disabled');
53
+ const badge = this.getAttribute('badge') ?? '';
54
+ const badgeVariant = this.getAttribute('badge-variant') ?? 'primary';
55
+ const indent = parseInt(this.getAttribute('indent') ?? '0', 10);
56
+ const target = this.getAttribute('target') ?? '_self';
57
+ const iconHtml = NAV_ICONS[iconName]
58
+ ? svgWrap(NAV_ICONS[iconName])
59
+ : iconName.startsWith('<') ? iconName : '';
60
+ const badgeColors = {
61
+ primary: ['var(--nc-primary)', 'var(--nc-white)'],
62
+ success: ['var(--nc-success)', 'var(--nc-white)'],
63
+ danger: ['var(--nc-danger)', 'var(--nc-white)'],
64
+ warning: ['var(--nc-warning)', 'var(--nc-text)'],
65
+ };
66
+ const [bgColor, textColor] = badgeColors[badgeVariant] ?? badgeColors.primary;
67
+ const paddingLeft = `calc(var(--nc-spacing-md) + ${indent * 16}px)`;
68
+ const tag = href ? 'a' : 'button';
69
+ const tagAttrs = href
70
+ ? `href="${href}" target="${target}"`
71
+ : `type="button"`;
72
+ return `
73
+ <style>
74
+ :host { display: block; }
75
+ ${tag} {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: var(--nc-spacing-sm);
79
+ width: 100%;
80
+ padding: 9px ${paddingLeft === 'calc(var(--nc-spacing-md) + 0px)' ? 'var(--nc-spacing-md)' : `var(--nc-spacing-md) var(--nc-spacing-md) var(--nc-spacing-md) ${paddingLeft}`};
81
+ border-radius: var(--nc-radius-md);
82
+ text-decoration: none;
83
+ font-family: var(--nc-font-family);
84
+ font-size: var(--nc-font-size-sm);
85
+ font-weight: ${active ? 'var(--nc-font-weight-semibold)' : 'var(--nc-font-weight-normal)'};
86
+ color: ${active ? 'var(--nc-primary)' : disabled ? 'var(--nc-text-muted)' : 'var(--nc-text-secondary)'};
87
+ background: ${active ? 'rgba(var(--nc-primary-rgb, 99,102,241), 0.1)' : 'transparent'};
88
+ border: none;
89
+ cursor: ${disabled ? 'not-allowed' : 'pointer'};
90
+ opacity: ${disabled ? 0.5 : 1};
91
+ text-align: left;
92
+ outline: none;
93
+ transition: background var(--nc-transition-fast), color var(--nc-transition-fast);
94
+ user-select: none;
95
+ position: relative;
96
+ }
97
+ ${active ? `${tag}::before { content: ''; position: absolute; left: 0; top: 20%; height: 60%; width: 3px; background: var(--nc-primary); border-radius: 0 2px 2px 0; }` : ''}
98
+ ${tag}:hover:not([disabled]) {
99
+ background: ${active ? 'rgba(var(--nc-primary-rgb, 99,102,241), 0.14)' : 'var(--nc-bg-secondary)'};
100
+ color: ${active ? 'var(--nc-primary)' : 'var(--nc-text)'};
101
+ }
102
+ ${tag}:focus-visible { outline: 2px solid var(--nc-primary); outline-offset: -2px; }
103
+ .icon { flex-shrink: 0; display: flex; opacity: ${active ? 1 : 0.65}; }
104
+ .label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
105
+ .badge {
106
+ flex-shrink: 0;
107
+ background: ${bgColor};
108
+ color: ${textColor};
109
+ font-size: 10px;
110
+ font-weight: var(--nc-font-weight-semibold);
111
+ line-height: 1;
112
+ padding: 2px 6px;
113
+ border-radius: 99px;
114
+ min-width: 18px;
115
+ text-align: center;
116
+ }
117
+ </style>
118
+ <${tag} ${tagAttrs} ${disabled ? (href ? 'aria-disabled="true"' : 'disabled') : ''} aria-current="${active ? 'page' : 'false'}">
119
+ ${iconHtml ? `<span class="icon">${iconHtml}<slot name="icon"></slot></span>` : '<slot name="icon"></slot>'}
120
+ <span class="label">${label}<slot></slot></span>
121
+ ${badge ? `<span class="badge">${badge}<slot name="badge"></slot></span>` : '<slot name="badge"></slot>'}
122
+ </${tag}>
123
+ `;
124
+ }
125
+ onMount() {
126
+ this.shadowRoot.addEventListener('click', (e) => {
127
+ if (this.hasAttribute('disabled')) {
128
+ e.preventDefault();
129
+ return;
130
+ }
131
+ this.dispatchEvent(new CustomEvent('nav-click', {
132
+ detail: { href: this.getAttribute('href') },
133
+ bubbles: true, composed: true,
134
+ }));
135
+ });
136
+ }
137
+ attributeChangedCallback(n, o, v) {
138
+ if (o !== v && this._mounted)
139
+ this.render();
140
+ }
141
+ }
142
+ defineComponent('nc-nav-item', NcNavItem);
@@ -0,0 +1,22 @@
1
+ import { Component } from '../core/component.js';
2
+ export declare class NcNumberInput extends Component {
3
+ static useShadowDOM: boolean;
4
+ static attributeOptions: {
5
+ variant: string[];
6
+ size: string[];
7
+ };
8
+ static get observedAttributes(): string[];
9
+ private holdTimer;
10
+ private holdInterval;
11
+ private getNumber;
12
+ private getCurrentValue;
13
+ private clamp;
14
+ template(): string;
15
+ onMount(): void;
16
+ private bindEvents;
17
+ private step;
18
+ private setValue;
19
+ private updateButtons;
20
+ onUnmount(): void;
21
+ attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
22
+ }
@@ -0,0 +1,270 @@
1
+ import { Component, defineComponent } from '../core/component.js';
2
+ export class NcNumberInput extends Component {
3
+ static useShadowDOM = true;
4
+ static attributeOptions = {
5
+ variant: ['default', 'filled'],
6
+ size: ['sm', 'md', 'lg']
7
+ };
8
+ static get observedAttributes() {
9
+ return ['name', 'value', 'min', 'max', 'step', 'placeholder', 'disabled', 'readonly', 'size', 'variant'];
10
+ }
11
+ holdTimer = null;
12
+ holdInterval = null;
13
+ getNumber(attr, fallback) {
14
+ const value = this.getAttribute(attr);
15
+ return value !== null && value !== '' ? Number(value) : fallback;
16
+ }
17
+ getCurrentValue() {
18
+ const value = this.getAttribute('value');
19
+ return value !== null && value !== '' ? Number(value) : 0;
20
+ }
21
+ clamp(value) {
22
+ const min = this.getAttribute('min');
23
+ const max = this.getAttribute('max');
24
+ if (min !== null && value < Number(min))
25
+ return Number(min);
26
+ if (max !== null && value > Number(max))
27
+ return Number(max);
28
+ return value;
29
+ }
30
+ template() {
31
+ const value = this.getCurrentValue();
32
+ const placeholder = this.getAttribute('placeholder') || '';
33
+ const disabled = this.hasAttribute('disabled');
34
+ const readonly = this.hasAttribute('readonly');
35
+ const min = this.getAttribute('min');
36
+ const max = this.getAttribute('max');
37
+ const step = this.getNumber('step', 1);
38
+ const atMin = min !== null && value <= Number(min);
39
+ const atMax = max !== null && value >= Number(max);
40
+ return `
41
+ <style>
42
+ :host { display: inline-flex; font-family: var(--nc-font-family); }
43
+ .wrap {
44
+ display: inline-flex;
45
+ align-items: stretch;
46
+ border: var(--nc-input-border, 1px solid #d1d5db);
47
+ border-radius: var(--nc-input-radius, 0.5rem);
48
+ overflow: hidden;
49
+ transition: border-color var(--nc-transition-fast, 160ms ease), box-shadow var(--nc-transition-fast, 160ms ease);
50
+ background: var(--nc-bg, #ffffff);
51
+ opacity: ${disabled ? '0.5' : '1'};
52
+ width: 100%;
53
+ }
54
+ :host([variant="filled"]) .wrap {
55
+ background: var(--nc-bg-tertiary, #f3f4f6);
56
+ border-color: transparent;
57
+ }
58
+ .wrap:focus-within {
59
+ border-color: var(--nc-input-focus-border, #10b981);
60
+ box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
61
+ }
62
+ .btn {
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ flex-shrink: 0;
67
+ background: var(--nc-bg-secondary, #f8fafc);
68
+ border: none;
69
+ cursor: ${disabled || readonly ? 'not-allowed' : 'pointer'};
70
+ color: var(--nc-text-muted, #6b7280);
71
+ transition: background var(--nc-transition-fast, 160ms ease), color var(--nc-transition-fast, 160ms ease);
72
+ user-select: none;
73
+ -webkit-user-select: none;
74
+ padding: 0;
75
+ }
76
+ :host([size="sm"]) .btn { width: 28px; }
77
+ :host([size="lg"]) .btn { width: 40px; }
78
+ .btn { width: 34px; }
79
+ .btn:hover:not(:disabled):not([aria-disabled="true"]) {
80
+ background: var(--nc-bg-tertiary, #f3f4f6);
81
+ color: var(--nc-text, #111827);
82
+ }
83
+ .btn:active:not(:disabled):not([aria-disabled="true"]) {
84
+ background: var(--nc-border, #d1d5db);
85
+ }
86
+ .btn[aria-disabled="true"] {
87
+ opacity: 0.35;
88
+ cursor: not-allowed;
89
+ }
90
+ input[type="number"] {
91
+ flex: 1;
92
+ min-width: 0;
93
+ border: none;
94
+ outline: none;
95
+ background: transparent;
96
+ color: var(--nc-text, #111827);
97
+ font-size: var(--nc-font-size-base, 1rem);
98
+ font-family: var(--nc-font-family);
99
+ text-align: center;
100
+ padding: 0;
101
+ cursor: ${disabled ? 'not-allowed' : 'auto'};
102
+ -moz-appearance: textfield;
103
+ }
104
+ input[type="number"]::-webkit-outer-spin-button,
105
+ input[type="number"]::-webkit-inner-spin-button {
106
+ -webkit-appearance: none;
107
+ margin: 0;
108
+ }
109
+ input[type="number"]::placeholder { color: var(--nc-text-muted, #6b7280); }
110
+ :host([size="sm"]) input { font-size: var(--nc-font-size-sm, 0.875rem); padding: var(--nc-spacing-xs, 0.25rem) 0; }
111
+ :host([size="md"]) input,
112
+ :host input { padding: var(--nc-spacing-sm, 0.5rem) 0; }
113
+ :host([size="lg"]) input { font-size: var(--nc-font-size-lg, 1.125rem); padding: var(--nc-spacing-md, 1rem) 0; }
114
+ </style>
115
+
116
+ <div class="wrap">
117
+ <button class="btn btn-dec" type="button" aria-label="Decrease" aria-disabled="${atMin || disabled || readonly}" tabindex="${disabled ? '-1' : '0'}">
118
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" width="14" height="14"><path d="M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
119
+ </button>
120
+ <input type="number" value="${value}" ${min !== null ? `min="${min}"` : ''} ${max !== null ? `max="${max}"` : ''} step="${step}" ${disabled ? 'disabled' : ''} ${readonly ? 'readonly' : ''} placeholder="${placeholder}" name="${this.getAttribute('name') || ''}" aria-label="${this.getAttribute('name') || 'number'}" />
121
+ <button class="btn btn-inc" type="button" aria-label="Increase" aria-disabled="${atMax || disabled || readonly}" tabindex="${disabled ? '-1' : '0'}">
122
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" width="14" height="14"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
123
+ </button>
124
+ </div>
125
+ `;
126
+ }
127
+ onMount() {
128
+ this.bindEvents();
129
+ }
130
+ bindEvents() {
131
+ const shadowRoot = this.shadowRoot;
132
+ if (!shadowRoot)
133
+ return;
134
+ const input = shadowRoot.querySelector('input[type="number"]');
135
+ const decreaseButton = shadowRoot.querySelector('.btn-dec');
136
+ const increaseButton = shadowRoot.querySelector('.btn-inc');
137
+ if (!input || !decreaseButton || !increaseButton)
138
+ return;
139
+ input.addEventListener('input', () => {
140
+ const value = this.clamp(Number(input.value));
141
+ this.updateButtons(value);
142
+ this.dispatchEvent(new CustomEvent('input', {
143
+ bubbles: true,
144
+ composed: true,
145
+ detail: { value, name: this.getAttribute('name') || '' }
146
+ }));
147
+ });
148
+ input.addEventListener('change', () => {
149
+ const value = this.clamp(Number(input.value));
150
+ input.value = String(value);
151
+ this.setAttribute('value', String(value));
152
+ this.updateButtons(value);
153
+ this.dispatchEvent(new CustomEvent('change', {
154
+ bubbles: true,
155
+ composed: true,
156
+ detail: { value, name: this.getAttribute('name') || '' }
157
+ }));
158
+ });
159
+ input.addEventListener('wheel', event => {
160
+ if (document.activeElement !== this && !this.shadowRoot?.activeElement)
161
+ return;
162
+ event.preventDefault();
163
+ const delta = event.deltaY;
164
+ if (delta < 0) {
165
+ this.step(1);
166
+ }
167
+ else {
168
+ this.step(-1);
169
+ }
170
+ }, { passive: false });
171
+ const setupHold = (button, direction) => {
172
+ button.addEventListener('mousedown', event => {
173
+ if (event.button !== 0)
174
+ return;
175
+ if (button.getAttribute('aria-disabled') === 'true')
176
+ return;
177
+ this.step(direction);
178
+ this.holdTimer = setTimeout(() => {
179
+ this.holdInterval = setInterval(() => this.step(direction), 80);
180
+ }, 400);
181
+ });
182
+ };
183
+ setupHold(decreaseButton, -1);
184
+ setupHold(increaseButton, 1);
185
+ const stopHold = () => {
186
+ if (this.holdTimer) {
187
+ clearTimeout(this.holdTimer);
188
+ this.holdTimer = null;
189
+ }
190
+ if (this.holdInterval) {
191
+ clearInterval(this.holdInterval);
192
+ this.holdInterval = null;
193
+ }
194
+ };
195
+ document.addEventListener('mouseup', stopHold);
196
+ decreaseButton.addEventListener('click', () => {
197
+ if (decreaseButton.getAttribute('aria-disabled') !== 'true')
198
+ this.step(-1);
199
+ });
200
+ increaseButton.addEventListener('click', () => {
201
+ if (increaseButton.getAttribute('aria-disabled') !== 'true')
202
+ this.step(1);
203
+ });
204
+ input.addEventListener('keydown', (event) => {
205
+ if (event.key === 'ArrowUp') {
206
+ event.preventDefault();
207
+ this.step(1);
208
+ }
209
+ if (event.key === 'ArrowDown') {
210
+ event.preventDefault();
211
+ this.step(-1);
212
+ }
213
+ });
214
+ }
215
+ step(direction) {
216
+ if (this.hasAttribute('disabled') || this.hasAttribute('readonly'))
217
+ return;
218
+ const step = this.getNumber('step', 1);
219
+ const decimals = step.toString().split('.')[1]?.length ?? 0;
220
+ const next = this.clamp(parseFloat((this.getCurrentValue() + direction * step).toFixed(decimals)));
221
+ this.setValue(next);
222
+ }
223
+ setValue(value) {
224
+ const input = this.shadowRoot?.querySelector('input[type="number"]');
225
+ if (input)
226
+ input.value = String(value);
227
+ this.setAttribute('value', String(value));
228
+ this.updateButtons(value);
229
+ this.dispatchEvent(new CustomEvent('change', {
230
+ bubbles: true,
231
+ composed: true,
232
+ detail: { value, name: this.getAttribute('name') || '' }
233
+ }));
234
+ }
235
+ updateButtons(value) {
236
+ const shadowRoot = this.shadowRoot;
237
+ if (!shadowRoot)
238
+ return;
239
+ const min = this.getAttribute('min');
240
+ const max = this.getAttribute('max');
241
+ const decreaseButton = shadowRoot.querySelector('.btn-dec');
242
+ const increaseButton = shadowRoot.querySelector('.btn-inc');
243
+ if (decreaseButton)
244
+ decreaseButton.setAttribute('aria-disabled', String(min !== null && value <= Number(min)));
245
+ if (increaseButton)
246
+ increaseButton.setAttribute('aria-disabled', String(max !== null && value >= Number(max)));
247
+ }
248
+ onUnmount() {
249
+ if (this.holdTimer)
250
+ clearTimeout(this.holdTimer);
251
+ if (this.holdInterval)
252
+ clearInterval(this.holdInterval);
253
+ }
254
+ attributeChangedCallback(name, oldValue, newValue) {
255
+ if (oldValue === newValue)
256
+ return;
257
+ if (name === 'value' && this._mounted) {
258
+ const input = this.shadowRoot?.querySelector('input[type="number"]');
259
+ if (input)
260
+ input.value = newValue || '0';
261
+ this.updateButtons(Number(newValue || 0));
262
+ return;
263
+ }
264
+ if (this._mounted) {
265
+ this.render();
266
+ this.bindEvents();
267
+ }
268
+ }
269
+ }
270
+ defineComponent('nc-number-input', NcNumberInput);
@@ -0,0 +1,41 @@
1
+ /**
2
+ * NcOtpInput Component - One-time password / verification code input
3
+ *
4
+ * Attributes:
5
+ * length - number of boxes (default: 6)
6
+ * type - 'numeric'(default)|'alphanumeric'|'alpha'
7
+ * separator - insert a visual dash/space separator after this position (e.g. "3" for 3+3)
8
+ * disabled - boolean
9
+ * masked - boolean - mask input like a password
10
+ * autofocus - boolean - focus first box on mount
11
+ * label - accessible label
12
+ * error - error message
13
+ * hint - helper text
14
+ *
15
+ * Value (read/write via property):
16
+ * el.value - get/set current OTP string
17
+ *
18
+ * Events:
19
+ * change - CustomEvent<{ value: string; complete: boolean }>
20
+ * complete - CustomEvent<{ value: string }> - fired when all boxes are filled
21
+ *
22
+ * Usage:
23
+ * <nc-otp-input length="6" type="numeric"></nc-otp-input>
24
+ */
25
+ import { Component } from '../core/component.js';
26
+ export declare class NcOtpInput extends Component {
27
+ static useShadowDOM: boolean;
28
+ private _values;
29
+ static get observedAttributes(): string[];
30
+ get value(): string;
31
+ set value(v: string);
32
+ private _length;
33
+ template(): string;
34
+ onMount(): void;
35
+ private _bindEvents;
36
+ private _applyFilledClass;
37
+ private _emitChange;
38
+ private _boxes;
39
+ private _boxAt;
40
+ attributeChangedCallback(name: string, oldVal: string, newVal: string): void;
41
+ }