create-nativecore 0.1.1 → 0.2.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 (175) hide show
  1. package/README.md +6 -14
  2. package/bin/index.mjs +402 -431
  3. package/package.json +3 -2
  4. package/template/.env.example +28 -0
  5. package/template/.htmlhintrc +14 -0
  6. package/template/api/data/dashboard.json +11 -0
  7. package/template/api/data/users.json +18 -0
  8. package/template/api/mockApi.js +161 -0
  9. package/template/assets/icon.svg +13 -0
  10. package/template/assets/logo.svg +25 -0
  11. package/template/eslint.config.js +94 -0
  12. package/template/index.html +137 -0
  13. package/template/manifest.json +19 -0
  14. package/template/public/.well-known/security.txt +9 -0
  15. package/template/public/_headers +24 -0
  16. package/template/public/_redirects +14 -0
  17. package/template/public/assets/icon.svg +13 -0
  18. package/template/public/assets/logo.svg +25 -0
  19. package/template/public/manifest.json +19 -0
  20. package/template/public/robots.txt +13 -0
  21. package/template/public/sitemap.xml +27 -0
  22. package/template/scripts/build-for-bots.mjs +121 -0
  23. package/template/scripts/convert-to-ts.mjs +106 -0
  24. package/template/scripts/fix-encoding.mjs +38 -0
  25. package/template/scripts/fix-svg-paths.mjs +32 -0
  26. package/template/scripts/generate-cf-router.mjs +52 -0
  27. package/template/scripts/inject-dev-tools.mjs +41 -0
  28. package/template/scripts/inject-version.mjs +65 -0
  29. package/template/scripts/make-component.mjs +445 -0
  30. package/template/scripts/make-component.mjs.backup +432 -0
  31. package/template/scripts/make-controller.mjs +119 -0
  32. package/template/scripts/make-core-component.mjs +303 -0
  33. package/template/scripts/make-view.mjs +346 -0
  34. package/template/scripts/minify.mjs +71 -0
  35. package/template/scripts/prepare-static-assets.mjs +141 -0
  36. package/template/scripts/prompt-bot-build.mjs +223 -0
  37. package/template/scripts/remove-component.mjs +170 -0
  38. package/template/scripts/remove-core-component.mjs +156 -0
  39. package/template/scripts/remove-dev.mjs +13 -0
  40. package/template/scripts/remove-view.mjs +200 -0
  41. package/template/scripts/strip-dev-blocks.mjs +30 -0
  42. package/template/scripts/watch-compile.mjs +69 -0
  43. package/template/server.js +1066 -0
  44. package/template/src/app.ts +115 -0
  45. package/template/src/components/appRegistry.ts +8 -0
  46. package/template/src/components/core/app-footer.ts +27 -0
  47. package/template/src/components/core/app-header.ts +175 -0
  48. package/template/src/components/core/app-sidebar.ts +238 -0
  49. package/template/src/components/core/loading-spinner.ts +25 -0
  50. package/template/src/components/core/nc-a.ts +313 -0
  51. package/template/src/components/core/nc-accordion.ts +186 -0
  52. package/template/src/components/core/nc-alert.ts +153 -0
  53. package/template/src/components/core/nc-animation.ts +1150 -0
  54. package/template/src/components/core/nc-autocomplete.ts +271 -0
  55. package/template/src/components/core/nc-avatar-group.ts +113 -0
  56. package/template/src/components/core/nc-avatar.ts +148 -0
  57. package/template/src/components/core/nc-badge.ts +86 -0
  58. package/template/src/components/core/nc-bottom-nav.ts +214 -0
  59. package/template/src/components/core/nc-breadcrumb.ts +96 -0
  60. package/template/src/components/core/nc-button.ts +307 -0
  61. package/template/src/components/core/nc-card.ts +160 -0
  62. package/template/src/components/core/nc-checkbox.ts +282 -0
  63. package/template/src/components/core/nc-chip.ts +115 -0
  64. package/template/src/components/core/nc-code.ts +314 -0
  65. package/template/src/components/core/nc-collapsible.ts +154 -0
  66. package/template/src/components/core/nc-color-picker.ts +268 -0
  67. package/template/src/components/core/nc-copy-button.ts +119 -0
  68. package/template/src/components/core/nc-date-picker.ts +443 -0
  69. package/template/src/components/core/nc-div.ts +280 -0
  70. package/template/src/components/core/nc-divider.ts +81 -0
  71. package/template/src/components/core/nc-drawer.ts +230 -0
  72. package/template/src/components/core/nc-dropdown.ts +178 -0
  73. package/template/src/components/core/nc-empty-state.ts +134 -0
  74. package/template/src/components/core/nc-file-upload.ts +354 -0
  75. package/template/src/components/core/nc-form.ts +312 -0
  76. package/template/src/components/core/nc-image.ts +184 -0
  77. package/template/src/components/core/nc-input.ts +383 -0
  78. package/template/src/components/core/nc-kbd.ts +48 -0
  79. package/template/src/components/core/nc-menu-item.ts +193 -0
  80. package/template/src/components/core/nc-menu.ts +376 -0
  81. package/template/src/components/core/nc-modal.ts +238 -0
  82. package/template/src/components/core/nc-nav-item.ts +151 -0
  83. package/template/src/components/core/nc-number-input.ts +350 -0
  84. package/template/src/components/core/nc-otp-input.ts +235 -0
  85. package/template/src/components/core/nc-pagination.ts +178 -0
  86. package/template/src/components/core/nc-popover.ts +260 -0
  87. package/template/src/components/core/nc-progress-circular.ts +119 -0
  88. package/template/src/components/core/nc-progress.ts +134 -0
  89. package/template/src/components/core/nc-radio.ts +235 -0
  90. package/template/src/components/core/nc-rating.ts +266 -0
  91. package/template/src/components/core/nc-rich-text.ts +283 -0
  92. package/template/src/components/core/nc-scroll-top.ts +116 -0
  93. package/template/src/components/core/nc-select.ts +452 -0
  94. package/template/src/components/core/nc-skeleton.ts +107 -0
  95. package/template/src/components/core/nc-slider.ts +285 -0
  96. package/template/src/components/core/nc-snackbar.ts +230 -0
  97. package/template/src/components/core/nc-splash.ts +343 -0
  98. package/template/src/components/core/nc-stepper.ts +247 -0
  99. package/template/src/components/core/nc-switch.ts +281 -0
  100. package/template/src/components/core/nc-tab-item.ts +138 -0
  101. package/template/src/components/core/nc-table.ts +279 -0
  102. package/template/src/components/core/nc-tabs.ts +554 -0
  103. package/template/src/components/core/nc-tag-input.ts +279 -0
  104. package/template/src/components/core/nc-textarea.ts +216 -0
  105. package/template/src/components/core/nc-time-picker.ts +438 -0
  106. package/template/src/components/core/nc-timeline.ts +186 -0
  107. package/template/src/components/core/nc-tooltip.ts +143 -0
  108. package/template/src/components/frameworkRegistry.ts +68 -0
  109. package/template/src/components/preloadRegistry.ts +28 -0
  110. package/template/src/components/registry.ts +8 -0
  111. package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
  112. package/template/src/constants/apiEndpoints.ts +27 -0
  113. package/template/src/constants/errorMessages.ts +23 -0
  114. package/template/src/constants/index.ts +8 -0
  115. package/template/src/constants/routePaths.ts +15 -0
  116. package/template/src/constants/storageKeys.ts +18 -0
  117. package/template/src/controllers/dashboard.controller.ts +200 -0
  118. package/template/src/controllers/home.controller.ts +21 -0
  119. package/template/src/controllers/index.ts +11 -0
  120. package/template/src/controllers/login.controller.ts +131 -0
  121. package/template/src/core/component.ts +354 -0
  122. package/template/src/core/errorHandler.ts +85 -0
  123. package/template/src/core/gpu-animation.ts +604 -0
  124. package/template/src/core/http.ts +173 -0
  125. package/template/src/core/lazyComponents.ts +90 -0
  126. package/template/src/core/router.ts +642 -0
  127. package/template/src/core/signals.ts +146 -0
  128. package/template/src/core/state.ts +248 -0
  129. package/template/src/dev/component-editor.ts +1363 -0
  130. package/template/src/dev/component-overlay.ts +278 -0
  131. package/template/src/dev/context-menu.ts +223 -0
  132. package/template/src/dev/denc-tools.ts +250 -0
  133. package/template/src/dev/hmr.ts +189 -0
  134. package/template/src/dev/nfbs.code-workspace +27 -0
  135. package/template/src/dev/outline-panel.ts +1247 -0
  136. package/template/src/middleware/auth.middleware.ts +23 -0
  137. package/template/src/routes/routes.ts +38 -0
  138. package/template/src/services/api.service.ts +394 -0
  139. package/template/src/services/auth.service.ts +176 -0
  140. package/template/src/services/index.ts +8 -0
  141. package/template/src/services/logger.service.ts +74 -0
  142. package/template/src/services/storage.service.ts +88 -0
  143. package/template/src/stores/appStore.ts +57 -0
  144. package/template/src/stores/uiStore.ts +36 -0
  145. package/template/src/styles/core-variables.css +219 -0
  146. package/template/src/styles/core.css +710 -0
  147. package/template/src/styles/main.css +3164 -0
  148. package/template/src/styles/variables.css +152 -0
  149. package/template/src/types/global.d.ts +47 -0
  150. package/template/src/utils/cacheBuster.ts +20 -0
  151. package/template/src/utils/dom.ts +149 -0
  152. package/template/src/utils/events.ts +203 -0
  153. package/template/src/utils/form.ts +176 -0
  154. package/template/src/utils/formatters.ts +169 -0
  155. package/template/src/utils/helpers.ts +195 -0
  156. package/template/src/utils/markdown.ts +307 -0
  157. package/template/src/utils/sidebar.ts +96 -0
  158. package/template/src/utils/smoothScroll.ts +85 -0
  159. package/template/src/utils/templates.ts +23 -0
  160. package/template/src/utils/validation.ts +73 -0
  161. package/template/src/views/protected/dashboard.html +293 -0
  162. package/template/src/views/public/home.html +150 -0
  163. package/template/src/views/public/login.html +102 -0
  164. package/template/tests/unit/component.test.ts +87 -0
  165. package/template/tests/unit/computed.test.ts +79 -0
  166. package/template/tests/unit/form.test.ts +68 -0
  167. package/template/tests/unit/formatters.test.ts +49 -0
  168. package/template/tests/unit/lazy-components.test.ts +59 -0
  169. package/template/tests/unit/markdown.test.ts +62 -0
  170. package/template/tests/unit/router.test.ts +112 -0
  171. package/template/tests/unit/signals.test.ts +54 -0
  172. package/template/tests/unit/validation.test.ts +50 -0
  173. package/template/tsconfig.build.json +21 -0
  174. package/template/tsconfig.json +51 -0
  175. package/template/vitest.config.ts +36 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * NcProgressCircular Component
3
+ *
4
+ * Circular/ring progress indicator.
5
+ *
6
+ * Attributes:
7
+ * - value: number — 0–max (required; omit or set indeterminate for spinner)
8
+ * - max: number — maximum value (default: 100)
9
+ * - size: number|string — diameter in px, or CSS size string (default: 48)
10
+ * - thickness: number — stroke width in px (default: 4)
11
+ * - variant: 'primary'|'success'|'warning'|'danger'|'neutral' (default: 'primary')
12
+ * - show-value: boolean — display percentage in centre
13
+ * - indeterminate: boolean — animated spinner mode
14
+ * - label: string — aria-label override
15
+ *
16
+ * Usage:
17
+ * <nc-progress-circular value="72"></nc-progress-circular>
18
+ * <nc-progress-circular indeterminate size="32"></nc-progress-circular>
19
+ * <nc-progress-circular value="45" show-value variant="success" size="64"></nc-progress-circular>
20
+ */
21
+
22
+ import { Component, defineComponent } from '@core/component.js';
23
+
24
+ const VARIANT_COLORS: Record<string, string> = {
25
+ primary: 'var(--nc-primary)',
26
+ success: 'var(--nc-success, #10b981)',
27
+ warning: 'var(--nc-warning, #f59e0b)',
28
+ danger: 'var(--nc-danger, #ef4444)',
29
+ neutral: 'var(--nc-text-muted)',
30
+ };
31
+
32
+ export class NcProgressCircular extends Component {
33
+ static useShadowDOM = true;
34
+
35
+ static get observedAttributes() {
36
+ return ['value', 'max', 'size', 'thickness', 'variant', 'show-value', 'indeterminate', 'label'];
37
+ }
38
+
39
+ template() {
40
+ const max = Number(this.getAttribute('max') || 100);
41
+ const value = Math.min(max, Math.max(0, Number(this.getAttribute('value') || 0)));
42
+ const pct = max > 0 ? Math.round((value / max) * 100) : 0;
43
+ const sizeAttr = this.getAttribute('size') || '48';
44
+ const size = Number(sizeAttr) || 48;
45
+ const thickness = Number(this.getAttribute('thickness') || 4);
46
+ const variant = this.getAttribute('variant') || 'primary';
47
+ const showValue = this.hasAttribute('show-value');
48
+ const indeterminate = this.hasAttribute('indeterminate');
49
+ const label = this.getAttribute('label') || (indeterminate ? 'Loading' : `${pct}%`);
50
+ const color = VARIANT_COLORS[variant] ?? VARIANT_COLORS.primary;
51
+
52
+ const r = (size - thickness) / 2;
53
+ const circumference = 2 * Math.PI * r;
54
+ const dashOffset = indeterminate ? circumference * 0.25 : circumference * (1 - pct / 100);
55
+ const cx = size / 2;
56
+ const cy = size / 2;
57
+ const fontSize = Math.max(10, Math.round(size * 0.22));
58
+
59
+ return `
60
+ <style>
61
+ :host { display: inline-flex; align-items: center; justify-content: center; }
62
+
63
+ svg { display: block; }
64
+
65
+ .track { fill: none; stroke: var(--nc-bg-tertiary); }
66
+ .fill { fill: none; stroke: ${color}; stroke-linecap: round; transition: stroke-dashoffset 0.4s ease; }
67
+
68
+ ${indeterminate ? `
69
+ @keyframes nc-spin {
70
+ from { transform: rotate(0deg); }
71
+ to { transform: rotate(360deg); }
72
+ }
73
+ svg { animation: nc-spin 1s linear infinite; transform-origin: center; }
74
+ .fill { stroke-dashoffset: ${dashOffset}; }
75
+ ` : ''}
76
+
77
+ .label {
78
+ font-family: var(--nc-font-family);
79
+ font-size: ${fontSize}px;
80
+ font-weight: var(--nc-font-weight-semibold);
81
+ fill: var(--nc-text);
82
+ dominant-baseline: central;
83
+ text-anchor: middle;
84
+ }
85
+ </style>
86
+ <svg
87
+ width="${size}"
88
+ height="${size}"
89
+ viewBox="0 0 ${size} ${size}"
90
+ role="progressbar"
91
+ aria-label="${label}"
92
+ aria-valuenow="${indeterminate ? '' : value}"
93
+ aria-valuemin="0"
94
+ aria-valuemax="${max}"
95
+ >
96
+ <circle
97
+ class="track"
98
+ cx="${cx}" cy="${cy}" r="${r}"
99
+ stroke-width="${thickness}"
100
+ />
101
+ <circle
102
+ class="fill"
103
+ cx="${cx}" cy="${cy}" r="${r}"
104
+ stroke-width="${thickness}"
105
+ stroke-dasharray="${circumference}"
106
+ stroke-dashoffset="${dashOffset}"
107
+ transform="rotate(-90 ${cx} ${cy})"
108
+ />
109
+ ${showValue && !indeterminate ? `<text class="label" x="${cx}" y="${cy}">${pct}%</text>` : ''}
110
+ </svg>
111
+ `;
112
+ }
113
+
114
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
115
+ if (oldValue !== newValue && this._mounted) this.render();
116
+ }
117
+ }
118
+
119
+ defineComponent('nc-progress-circular', NcProgressCircular);
@@ -0,0 +1,134 @@
1
+ /**
2
+ * NcProgress Component
3
+ *
4
+ * Attributes:
5
+ * - value: number — current value (0-100, or 0-max)
6
+ * - max: number — maximum value (default: 100)
7
+ * - variant: 'primary'|'success'|'warning'|'danger'|'neutral' (default: 'primary')
8
+ * - size: 'xs'|'sm'|'md'|'lg' (default: 'sm')
9
+ * - label: string — accessible label
10
+ * - show-value: boolean — display percentage text
11
+ * - indeterminate: boolean — animated indeterminate state (no value needed)
12
+ * - striped: boolean — striped fill
13
+ * - animated: boolean — animates the stripes (requires striped)
14
+ *
15
+ * Usage:
16
+ * <nc-progress value="60"></nc-progress>
17
+ * <nc-progress value="30" variant="success" show-value></nc-progress>
18
+ * <nc-progress indeterminate></nc-progress>
19
+ */
20
+
21
+ import { Component, defineComponent } from '@core/component.js';
22
+
23
+ export class NcProgress extends Component {
24
+ static useShadowDOM = true;
25
+
26
+ static get observedAttributes() {
27
+ return ['value', 'max', 'variant', 'size', 'label', 'show-value', 'indeterminate', 'striped', 'animated'];
28
+ }
29
+
30
+ template() {
31
+ const max = Number(this.getAttribute('max') || 100);
32
+ const value = Math.min(max, Math.max(0, Number(this.getAttribute('value') || 0)));
33
+ const pct = max > 0 ? Math.round((value / max) * 100) : 0;
34
+ const variant = this.getAttribute('variant') || 'primary';
35
+ const showValue = this.hasAttribute('show-value');
36
+ const indeterminate = this.hasAttribute('indeterminate');
37
+ const striped = this.hasAttribute('striped');
38
+ const animated = this.hasAttribute('animated');
39
+ const label = this.getAttribute('label') || `${pct}%`;
40
+
41
+ return `
42
+ <style>
43
+ :host { display: block; width: 100%; font-family: var(--nc-font-family); }
44
+
45
+ .wrap { display: flex; align-items: center; gap: var(--nc-spacing-sm); }
46
+
47
+ .track {
48
+ flex: 1;
49
+ background: var(--nc-bg-tertiary);
50
+ border-radius: 999px;
51
+ overflow: hidden;
52
+ position: relative;
53
+ }
54
+
55
+ :host([size="xs"]) .track { height: 4px; }
56
+ :host([size="sm"]) .track,
57
+ :host .track { height: 6px; }
58
+ :host([size="md"]) .track { height: 10px; }
59
+ :host([size="lg"]) .track { height: 16px; }
60
+
61
+ .bar {
62
+ height: 100%;
63
+ border-radius: 999px;
64
+ transition: width 0.4s ease;
65
+ width: ${indeterminate ? '40%' : `${pct}%`};
66
+ }
67
+
68
+ .bar--primary { background: var(--nc-primary); }
69
+ .bar--success { background: var(--nc-success, #10b981); }
70
+ .bar--warning { background: var(--nc-warning, #f59e0b); }
71
+ .bar--danger { background: var(--nc-danger, #ef4444); }
72
+ .bar--neutral { background: var(--nc-text-muted); }
73
+
74
+ ${striped ? `
75
+ .bar {
76
+ background-image: linear-gradient(
77
+ 45deg,
78
+ rgba(255,255,255,.15) 25%,
79
+ transparent 25%,
80
+ transparent 50%,
81
+ rgba(255,255,255,.15) 50%,
82
+ rgba(255,255,255,.15) 75%,
83
+ transparent 75%,
84
+ transparent
85
+ );
86
+ background-size: 1rem 1rem;
87
+ }` : ''}
88
+
89
+ ${animated ? `
90
+ @keyframes nc-progress-stripes {
91
+ from { background-position: 1rem 0; }
92
+ to { background-position: 0 0; }
93
+ }
94
+ .bar { animation: nc-progress-stripes 1s linear infinite; }` : ''}
95
+
96
+ ${indeterminate ? `
97
+ @keyframes nc-indeterminate {
98
+ 0% { left: -40%; }
99
+ 100% { left: 100%; }
100
+ }
101
+ .bar {
102
+ position: absolute;
103
+ animation: nc-indeterminate 1.4s ease infinite;
104
+ }` : ''}
105
+
106
+ .value-label {
107
+ font-size: var(--nc-font-size-xs);
108
+ color: var(--nc-text-muted);
109
+ min-width: 2.8ch;
110
+ text-align: right;
111
+ }
112
+ </style>
113
+ <div class="wrap">
114
+ <div
115
+ class="track"
116
+ role="progressbar"
117
+ aria-label="${label}"
118
+ aria-valuenow="${indeterminate ? '' : value}"
119
+ aria-valuemin="0"
120
+ aria-valuemax="${max}"
121
+ >
122
+ <div class="bar bar--${variant}"></div>
123
+ </div>
124
+ ${showValue && !indeterminate ? `<span class="value-label">${pct}%</span>` : ''}
125
+ </div>
126
+ `;
127
+ }
128
+
129
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
130
+ if (oldValue !== newValue && this._mounted) this.render();
131
+ }
132
+ }
133
+
134
+ defineComponent('nc-progress', NcProgress);
@@ -0,0 +1,235 @@
1
+ /**
2
+ * NcRadio Component
3
+ *
4
+ * NativeCore Framework Core Component
5
+ *
6
+ * Attributes:
7
+ * - label: string — text label shown next to the radio
8
+ * - name: string — radio group name (required for grouping)
9
+ * - value: string — value submitted with a form
10
+ * - checked: boolean — selected state
11
+ * - disabled: boolean — disabled state
12
+ * - size: 'sm' | 'md' | 'lg' (default: 'md')
13
+ * - variant: 'primary' | 'success' | 'danger' (default: 'primary')
14
+ *
15
+ * Events:
16
+ * - change: CustomEvent<{ value: string; name: string }>
17
+ *
18
+ * Usage:
19
+ * <nc-radio name="plan" value="free" label="Free" checked></nc-radio>
20
+ * <nc-radio name="plan" value="pro" label="Pro"></nc-radio>
21
+ * <nc-radio name="plan" value="enterprise" label="Enterprise" disabled></nc-radio>
22
+ */
23
+
24
+ import { Component, defineComponent } from '@core/component.js';
25
+
26
+ export class NcRadio extends Component {
27
+ static useShadowDOM = true;
28
+
29
+ static attributeOptions = {
30
+ variant: ['primary', 'success', 'danger'],
31
+ size: ['sm', 'md', 'lg']
32
+ };
33
+
34
+ static get observedAttributes() {
35
+ return ['label', 'name', 'value', 'checked', 'disabled', 'size', 'variant'];
36
+ }
37
+
38
+ constructor() {
39
+ super();
40
+ }
41
+
42
+ template() {
43
+ const label = this.getAttribute('label') || '';
44
+ const size = this.getAttribute('size') || 'md';
45
+ const checked = this.hasAttribute('checked');
46
+ const disabled = this.hasAttribute('disabled');
47
+
48
+ const dotSize = size === 'sm' ? '6px' : size === 'lg' ? '10px' : '8px';
49
+ const boxSize = size === 'sm' ? '16px' : size === 'lg' ? '24px' : '20px';
50
+
51
+ return `
52
+ <style>
53
+ :host {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: var(--nc-spacing-sm);
57
+ cursor: ${disabled ? 'not-allowed' : 'pointer'};
58
+ user-select: none;
59
+ font-family: var(--nc-font-family);
60
+ opacity: ${disabled ? '0.5' : '1'};
61
+ }
62
+
63
+ .radio-wrapper {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ gap: var(--nc-spacing-sm);
67
+ }
68
+
69
+ input[type="radio"] {
70
+ position: absolute;
71
+ opacity: 0;
72
+ width: 0;
73
+ height: 0;
74
+ pointer-events: none;
75
+ }
76
+
77
+ .ring {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ flex-shrink: 0;
82
+ width: ${boxSize};
83
+ height: ${boxSize};
84
+ border-radius: var(--nc-radius-full);
85
+ border: 2px solid var(--nc-border-dark);
86
+ background: var(--nc-bg);
87
+ transition: all var(--nc-transition-fast);
88
+ box-sizing: border-box;
89
+ position: relative;
90
+ }
91
+
92
+ .dot {
93
+ width: ${dotSize};
94
+ height: ${dotSize};
95
+ border-radius: var(--nc-radius-full);
96
+ background: var(--nc-white);
97
+ opacity: 0;
98
+ transform: scale(0);
99
+ transition: all var(--nc-transition-fast);
100
+ }
101
+
102
+ /* Checked state */
103
+ :host([checked]) .ring {
104
+ border-color: var(--nc-primary);
105
+ background: var(--nc-primary);
106
+ }
107
+
108
+ :host([checked]) .dot {
109
+ opacity: 1;
110
+ transform: scale(1);
111
+ }
112
+
113
+ :host([variant="success"][checked]) .ring {
114
+ border-color: var(--nc-success);
115
+ background: var(--nc-success);
116
+ }
117
+
118
+ :host([variant="danger"][checked]) .ring {
119
+ border-color: var(--nc-danger);
120
+ background: var(--nc-danger);
121
+ }
122
+
123
+ /* Hover */
124
+ :host(:not([disabled])) .ring:hover {
125
+ border-color: var(--nc-primary);
126
+ box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
127
+ }
128
+
129
+ :host([variant="success"]:not([disabled])) .ring:hover {
130
+ border-color: var(--nc-success);
131
+ box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
132
+ }
133
+
134
+ :host([variant="danger"]:not([disabled])) .ring:hover {
135
+ border-color: var(--nc-danger);
136
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
137
+ }
138
+
139
+ /* Focus ring */
140
+ :host(:focus-visible) .ring {
141
+ outline: 2px solid var(--nc-primary);
142
+ outline-offset: 2px;
143
+ }
144
+
145
+ .label {
146
+ font-size: var(--nc-font-size-base);
147
+ color: var(--nc-text);
148
+ line-height: var(--nc-line-height-normal);
149
+ }
150
+
151
+ :host([size="sm"]) .label {
152
+ font-size: var(--nc-font-size-sm);
153
+ }
154
+
155
+ :host([size="lg"]) .label {
156
+ font-size: var(--nc-font-size-lg);
157
+ }
158
+ </style>
159
+
160
+ <label class="radio-wrapper">
161
+ <input
162
+ type="radio"
163
+ ${checked ? 'checked' : ''}
164
+ ${disabled ? 'disabled' : ''}
165
+ name="${this.getAttribute('name') || ''}"
166
+ value="${this.getAttribute('value') || ''}"
167
+ />
168
+ <span class="ring">
169
+ <span class="dot"></span>
170
+ </span>
171
+ ${label ? `<span class="label">${label}</span>` : '<slot></slot>'}
172
+ </label>
173
+ `;
174
+ }
175
+
176
+ onMount() {
177
+ if (!this.hasAttribute('tabindex')) {
178
+ this.setAttribute('tabindex', '0');
179
+ }
180
+ this.setAttribute('role', 'radio');
181
+ this.setAttribute('aria-checked', String(this.hasAttribute('checked')));
182
+
183
+ this.shadowRoot!.addEventListener('click', () => {
184
+ if (this.hasAttribute('disabled')) return;
185
+ this._select();
186
+ });
187
+
188
+ this.addEventListener('keydown', (e: KeyboardEvent) => {
189
+ if (e.key === ' ' || e.key === 'Enter') {
190
+ e.preventDefault();
191
+ if (!this.hasAttribute('disabled')) this._select();
192
+ }
193
+ });
194
+ }
195
+
196
+ private _select() {
197
+ if (this.hasAttribute('checked')) return;
198
+
199
+ // Deselect siblings in the same named group
200
+ const name = this.getAttribute('name');
201
+ if (name) {
202
+ const root = (this.getRootNode() as Document | ShadowRoot);
203
+ const siblings = Array.from(root.querySelectorAll<NcRadio>(`nc-radio[name="${name}"]`));
204
+ siblings.forEach(sibling => {
205
+ if (sibling !== this) {
206
+ sibling.removeAttribute('checked');
207
+ sibling.setAttribute('aria-checked', 'false');
208
+ }
209
+ });
210
+ }
211
+
212
+ this.setAttribute('checked', '');
213
+ this.setAttribute('aria-checked', 'true');
214
+
215
+ this.dispatchEvent(new CustomEvent('change', {
216
+ bubbles: true,
217
+ composed: true,
218
+ detail: {
219
+ value: this.getAttribute('value') || '',
220
+ name: this.getAttribute('name') || ''
221
+ }
222
+ }));
223
+ }
224
+
225
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
226
+ if (oldValue !== newValue) {
227
+ this.render();
228
+ if (name === 'checked') {
229
+ this.setAttribute('aria-checked', String(this.hasAttribute('checked')));
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ defineComponent('nc-radio', NcRadio);