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,19 @@
1
+ import { Component } from '../core/component.js';
2
+ export declare class NcRating extends Component {
3
+ static useShadowDOM: boolean;
4
+ static attributeOptions: {
5
+ variant: string[];
6
+ size: string[];
7
+ };
8
+ static get observedAttributes(): string[];
9
+ private valueState;
10
+ private hovered;
11
+ private getMax;
12
+ private getValue;
13
+ template(): string;
14
+ onMount(): void;
15
+ private bindEvents;
16
+ private applyState;
17
+ private commit;
18
+ attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
19
+ }
@@ -0,0 +1,187 @@
1
+ import { Component, defineComponent } from '../core/component.js';
2
+ const ICONS = {
3
+ star: {
4
+ filled: `<svg class="icon-filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="1em" height="1em"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>`,
5
+ empty: `<svg class="icon-empty" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="1em" height="1em"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>`
6
+ },
7
+ heart: {
8
+ filled: `<svg class="icon-filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="1em" height="1em"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>`,
9
+ empty: `<svg class="icon-empty" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="1em" height="1em"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>`
10
+ },
11
+ circle: {
12
+ filled: `<svg class="icon-filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="1em" height="1em"><circle cx="12" cy="12" r="10"/></svg>`,
13
+ empty: `<svg class="icon-empty" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="1em" height="1em"><circle cx="12" cy="12" r="10"/></svg>`
14
+ }
15
+ };
16
+ export class NcRating extends Component {
17
+ static useShadowDOM = true;
18
+ static attributeOptions = {
19
+ variant: ['star', 'heart', 'circle'],
20
+ size: ['sm', 'md', 'lg']
21
+ };
22
+ static get observedAttributes() {
23
+ return ['name', 'max', 'readonly', 'disabled', 'size', 'variant', 'allow-clear'];
24
+ }
25
+ valueState = 0;
26
+ hovered = 0;
27
+ getMax() {
28
+ return Number(this.getAttribute('max') || 5);
29
+ }
30
+ getValue() {
31
+ return this.valueState;
32
+ }
33
+ template() {
34
+ if (!this._mounted) {
35
+ this.valueState = Number(this.getAttribute('value') || 0);
36
+ }
37
+ const value = this.getValue();
38
+ const max = this.getMax();
39
+ const variant = this.getAttribute('variant') || 'star';
40
+ const icon = ICONS[variant] ?? ICONS.star;
41
+ const readonly = this.hasAttribute('readonly');
42
+ const disabled = this.hasAttribute('disabled');
43
+ const interactive = !readonly && !disabled;
44
+ const items = Array.from({ length: max }, (_, index) => {
45
+ const position = index + 1;
46
+ const filled = position <= value;
47
+ return `<span class="item${filled ? ' filled' : ''}" data-pos="${position}" role="${interactive ? 'radio' : 'presentation'}" aria-checked="${filled}" aria-label="${position} of ${max}" tabindex="${interactive ? '0' : '-1'}">${icon.filled}${icon.empty}</span>`;
48
+ }).join('');
49
+ return `
50
+ <style>
51
+ :host {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ font-family: var(--nc-font-family);
55
+ }
56
+ .items {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: 2px;
60
+ }
61
+ :host,
62
+ :host([size="md"]) { font-size: 1.5rem; }
63
+ :host([size="sm"]) { font-size: 1rem; }
64
+ :host([size="lg"]) { font-size: 2rem; }
65
+ .item {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ cursor: ${interactive ? 'pointer' : 'default'};
70
+ color: var(--nc-gray-300, #d1d5db);
71
+ transition: color var(--nc-transition-fast, 160ms ease), transform var(--nc-transition-fast, 160ms ease);
72
+ opacity: ${disabled ? '0.4' : '1'};
73
+ line-height: 1;
74
+ pointer-events: ${interactive ? 'auto' : 'none'};
75
+ }
76
+ .item .icon-filled { display: none; }
77
+ .item .icon-empty { display: block; }
78
+ .item.filled .icon-filled { display: block; }
79
+ .item.filled .icon-empty { display: none; }
80
+ .item.filled,
81
+ .item.hovered { color: var(--nc-warning, #f59e0b); }
82
+ .item.hovered { transform: scale(1.2); }
83
+ .item.preview-filled .icon-filled { display: block; }
84
+ .item.preview-filled .icon-empty { display: none; }
85
+ .item.preview-empty .icon-filled { display: none; }
86
+ .item.preview-empty .icon-empty { display: block; }
87
+ .item:focus-visible {
88
+ outline: 2px solid var(--nc-primary, #10b981);
89
+ outline-offset: 2px;
90
+ border-radius: 2px;
91
+ }
92
+ </style>
93
+ <div class="items" role="${interactive ? 'radiogroup' : 'img'}" aria-label="Rating: ${value} of ${max}">${items}</div>
94
+ <input type="hidden" name="${this.getAttribute('name') || ''}" value="${value}" />
95
+ `;
96
+ }
97
+ onMount() {
98
+ this.bindEvents();
99
+ }
100
+ bindEvents() {
101
+ if (this.hasAttribute('readonly') || this.hasAttribute('disabled'))
102
+ return;
103
+ const container = this.$('.items');
104
+ if (!container)
105
+ return;
106
+ container.addEventListener('mouseover', event => {
107
+ const item = event.target.closest('.item');
108
+ if (!item)
109
+ return;
110
+ this.hovered = Number(item.dataset.pos);
111
+ this.applyState();
112
+ });
113
+ container.addEventListener('mouseleave', () => {
114
+ this.hovered = 0;
115
+ this.applyState();
116
+ });
117
+ container.addEventListener('click', event => {
118
+ const item = event.target.closest('.item');
119
+ if (!item)
120
+ return;
121
+ const position = Number(item.dataset.pos);
122
+ const next = this.hasAttribute('allow-clear') && position === this.getValue() ? 0 : position;
123
+ this.hovered = 0;
124
+ this.commit(next);
125
+ });
126
+ this.$$('.item').forEach(item => {
127
+ item.addEventListener('keydown', (event) => {
128
+ if (event.key === 'Enter' || event.key === ' ') {
129
+ event.preventDefault();
130
+ item.click();
131
+ }
132
+ if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
133
+ event.preventDefault();
134
+ this.commit(Math.min(this.getMax(), this.getValue() + 1));
135
+ }
136
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
137
+ event.preventDefault();
138
+ this.commit(Math.max(0, this.getValue() - 1));
139
+ }
140
+ });
141
+ });
142
+ }
143
+ applyState() {
144
+ const value = this.getValue();
145
+ const hovered = this.hovered;
146
+ this.$$('.item').forEach(item => {
147
+ const position = Number(item.dataset.pos);
148
+ if (hovered > 0) {
149
+ const previewFilled = position <= hovered;
150
+ item.classList.toggle('hovered', previewFilled);
151
+ item.classList.toggle('preview-filled', previewFilled);
152
+ item.classList.toggle('preview-empty', !previewFilled);
153
+ item.setAttribute('aria-checked', String(position <= value));
154
+ }
155
+ else {
156
+ item.classList.remove('hovered', 'preview-filled', 'preview-empty');
157
+ item.classList.toggle('filled', position <= value);
158
+ item.setAttribute('aria-checked', String(position <= value));
159
+ }
160
+ });
161
+ }
162
+ commit(value) {
163
+ this.valueState = value;
164
+ this.setAttribute('value', String(value));
165
+ const hidden = this.$('input[type="hidden"]');
166
+ if (hidden)
167
+ hidden.value = String(value);
168
+ const container = this.$('.items');
169
+ if (container)
170
+ container.setAttribute('aria-label', `Rating: ${value} of ${this.getMax()}`);
171
+ this.$$('.item').forEach(item => {
172
+ item.classList.toggle('filled', Number(item.dataset.pos) <= value);
173
+ });
174
+ this.dispatchEvent(new CustomEvent('change', {
175
+ bubbles: true,
176
+ composed: true,
177
+ detail: { value, name: this.getAttribute('name') || '' }
178
+ }));
179
+ }
180
+ attributeChangedCallback(name, oldValue, newValue) {
181
+ if (oldValue === newValue || !this._mounted)
182
+ return;
183
+ this.render();
184
+ this.bindEvents();
185
+ }
186
+ }
187
+ defineComponent('nc-rating', NcRating);
@@ -0,0 +1,43 @@
1
+ /**
2
+ * NcRichText Component
3
+ *
4
+ * A lightweight rich text editor built on contenteditable + execCommand,
5
+ * with a toolbar, HTML output, and zero external dependencies.
6
+ *
7
+ * Attributes:
8
+ * - name: string - for use in nc-form (emits HTML string as value)
9
+ * - value: string - initial HTML content
10
+ * - placeholder: string (default: 'Start typing...')
11
+ * - disabled: boolean
12
+ * - readonly: boolean
13
+ * - toolbar: string - comma-separated list of toolbar buttons to show.
14
+ * Full list: 'bold,italic,underline,strike,|,h1,h2,h3,|,ul,ol,|,blockquote,code,|,link,|,align-left,align-center,align-right,|,clear'
15
+ * Default: all of the above
16
+ * - min-height: string - CSS min-height of editable area (default: '120px')
17
+ * - max-height: string - CSS max-height of editable area (default: '400px')
18
+ *
19
+ * Events:
20
+ * - input: CustomEvent<{ value: string; name: string }>
21
+ * - change: CustomEvent<{ value: string; name: string }>
22
+ *
23
+ * Usage:
24
+ * <nc-rich-text name="body" placeholder="Write something..." min-height="200px"></nc-rich-text>
25
+ * <nc-rich-text name="notes" toolbar="bold,italic,underline,|,ul,ol"></nc-rich-text>
26
+ */
27
+ import { Component } from '../core/component.js';
28
+ export declare class NcRichText extends Component {
29
+ static useShadowDOM: boolean;
30
+ static get observedAttributes(): string[];
31
+ private _value;
32
+ constructor();
33
+ private _getToolbarItems;
34
+ template(): string;
35
+ onMount(): void;
36
+ private _exec;
37
+ private _onContentChange;
38
+ private _updateToolbarState;
39
+ private _bindEvents;
40
+ getValue(): string;
41
+ setValue(html: string): void;
42
+ attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
43
+ }
@@ -0,0 +1,310 @@
1
+ /**
2
+ * NcRichText Component
3
+ *
4
+ * A lightweight rich text editor built on contenteditable + execCommand,
5
+ * with a toolbar, HTML output, and zero external dependencies.
6
+ *
7
+ * Attributes:
8
+ * - name: string - for use in nc-form (emits HTML string as value)
9
+ * - value: string - initial HTML content
10
+ * - placeholder: string (default: 'Start typing...')
11
+ * - disabled: boolean
12
+ * - readonly: boolean
13
+ * - toolbar: string - comma-separated list of toolbar buttons to show.
14
+ * Full list: 'bold,italic,underline,strike,|,h1,h2,h3,|,ul,ol,|,blockquote,code,|,link,|,align-left,align-center,align-right,|,clear'
15
+ * Default: all of the above
16
+ * - min-height: string - CSS min-height of editable area (default: '120px')
17
+ * - max-height: string - CSS max-height of editable area (default: '400px')
18
+ *
19
+ * Events:
20
+ * - input: CustomEvent<{ value: string; name: string }>
21
+ * - change: CustomEvent<{ value: string; name: string }>
22
+ *
23
+ * Usage:
24
+ * <nc-rich-text name="body" placeholder="Write something..." min-height="200px"></nc-rich-text>
25
+ * <nc-rich-text name="notes" toolbar="bold,italic,underline,|,ul,ol"></nc-rich-text>
26
+ */
27
+ import { Component, defineComponent } from '../core/component.js';
28
+ const ICONS = {
29
+ bold: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14"><text x="3" y="13" font-family="Georgia,serif" font-size="13" font-weight="bold" fill="currentColor">B</text></svg>`,
30
+ italic: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14"><text x="4" y="13" font-family="Georgia,serif" font-size="13" font-style="italic" fill="currentColor">I</text></svg>`,
31
+ underline: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14"><text x="3" y="11" font-family="Arial,sans-serif" font-size="12" text-decoration="underline" fill="currentColor">U</text><line x1="2" y1="14" x2="14" y2="14" stroke="currentColor" stroke-width="1.5"/></svg>`,
32
+ strike: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14"><line x1="2" y1="8" x2="14" y2="8" stroke="currentColor" stroke-width="1.5"/><text x="3" y="7" font-family="Arial" font-size="9" fill="currentColor">S</text></svg>`,
33
+ h1: `<svg viewBox="0 0 20 16" fill="none" width="18" height="14"><text x="1" y="13" font-family="Arial" font-size="13" font-weight="bold" fill="currentColor">H1</text></svg>`,
34
+ h2: `<svg viewBox="0 0 20 16" fill="none" width="18" height="14"><text x="1" y="13" font-family="Arial" font-size="13" font-weight="bold" fill="currentColor">H2</text></svg>`,
35
+ h3: `<svg viewBox="0 0 20 16" fill="none" width="18" height="14"><text x="1" y="13" font-family="Arial" font-size="13" font-weight="bold" fill="currentColor">H3</text></svg>`,
36
+ ul: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"><circle cx="3" cy="5" r="1" fill="currentColor" stroke="none"/><line x1="6" y1="5" x2="14" y2="5"/><circle cx="3" cy="9" r="1" fill="currentColor" stroke="none"/><line x1="6" y1="9" x2="14" y2="9"/><circle cx="3" cy="13" r="1" fill="currentColor" stroke="none"/><line x1="6" y1="13" x2="14" y2="13"/></svg>`,
37
+ ol: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"><text x="1" y="6" font-family="Arial" font-size="5" fill="currentColor" stroke="none">1.</text><line x1="6" y1="5" x2="14" y2="5"/><text x="1" y="10" font-family="Arial" font-size="5" fill="currentColor" stroke="none">2.</text><line x1="6" y1="9" x2="14" y2="9"/><text x="1" y="14" font-family="Arial" font-size="5" fill="currentColor" stroke="none">3.</text><line x1="6" y1="13" x2="14" y2="13"/></svg>`,
38
+ blockquote: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14"><text x="1" y="13" font-family="Georgia,serif" font-size="16" fill="currentColor" opacity=".6">"</text></svg>`,
39
+ code: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"><path d="M5 4L1 8l4 4M11 4l4 4-4 4"/></svg>`,
40
+ link: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"><path d="M6.5 9.5a3.5 3.5 0 005 0l2-2a3.5 3.5 0 00-5-5l-1 1"/><path d="M9.5 6.5a3.5 3.5 0 00-5 0l-2 2a3.5 3.5 0 005 5l1-1"/></svg>`,
41
+ 'align-left': `<svg viewBox="0 0 16 16" fill="none" width="14" height="14" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"><line x1="2" y1="4" x2="14" y2="4"/><line x1="2" y1="8" x2="10" y2="8"/><line x1="2" y1="12" x2="14" y2="12"/></svg>`,
42
+ 'align-center': `<svg viewBox="0 0 16 16" fill="none" width="14" height="14" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"><line x1="2" y1="4" x2="14" y2="4"/><line x1="4" y1="8" x2="12" y2="8"/><line x1="2" y1="12" x2="14" y2="12"/></svg>`,
43
+ 'align-right': `<svg viewBox="0 0 16 16" fill="none" width="14" height="14" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"><line x1="2" y1="4" x2="14" y2="4"/><line x1="6" y1="8" x2="14" y2="8"/><line x1="2" y1="12" x2="14" y2="12"/></svg>`,
44
+ clear: `<svg viewBox="0 0 16 16" fill="none" width="14" height="14" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"><path d="M3 3l10 10M13 3L3 13"/></svg>`,
45
+ };
46
+ export class NcRichText extends Component {
47
+ static useShadowDOM = true;
48
+ static get observedAttributes() {
49
+ return ['name', 'value', 'placeholder', 'disabled', 'readonly', 'toolbar', 'min-height', 'max-height'];
50
+ }
51
+ _value = '';
52
+ constructor() { super(); }
53
+ _getToolbarItems() {
54
+ const raw = this.getAttribute('toolbar');
55
+ if (raw)
56
+ return raw.split(',').map(s => s.trim());
57
+ return ['bold', 'italic', 'underline', 'strike', '|', 'h1', 'h2', 'h3', '|', 'ul', 'ol', '|', 'blockquote', 'code', '|', 'link', '|', 'align-left', 'align-center', 'align-right', '|', 'clear'];
58
+ }
59
+ template() {
60
+ if (!this._mounted) {
61
+ this._value = this.getAttribute('value') || '';
62
+ }
63
+ const placeholder = this.getAttribute('placeholder') || 'Start typing...';
64
+ const disabled = this.hasAttribute('disabled');
65
+ const readonly = this.hasAttribute('readonly');
66
+ const minHeight = this.getAttribute('min-height') || '120px';
67
+ const maxHeight = this.getAttribute('max-height') || '400px';
68
+ const items = this._getToolbarItems();
69
+ const toolbarHtml = items.map(id => {
70
+ if (id === '|')
71
+ return `<span class="tb-sep"></span>`;
72
+ const icon = ICONS[id] ?? '';
73
+ return `<button class="tb-btn" type="button" data-cmd="${id}" title="${id}" tabindex="-1">${icon}</button>`;
74
+ }).join('');
75
+ return `
76
+ <style>
77
+ :host { display: block; font-family: var(--nc-font-family); }
78
+
79
+ .editor-wrap {
80
+ border: var(--nc-input-border);
81
+ border-radius: var(--nc-input-radius);
82
+ background: var(--nc-bg);
83
+ overflow: hidden;
84
+ opacity: ${disabled ? '0.5' : '1'};
85
+ pointer-events: ${disabled ? 'none' : 'auto'};
86
+ transition: border-color var(--nc-transition-fast), box-shadow var(--nc-transition-fast);
87
+ }
88
+ .editor-wrap:focus-within { border-color: var(--nc-input-focus-border); box-shadow: 0 0 0 3px rgba(16,185,129,.15); }
89
+
90
+ .toolbar {
91
+ display: flex;
92
+ flex-wrap: wrap;
93
+ align-items: center;
94
+ gap: 2px;
95
+ padding: 6px 8px;
96
+ border-bottom: 1px solid var(--nc-border);
97
+ background: var(--nc-bg-secondary);
98
+ user-select: none;
99
+ }
100
+
101
+ .tb-btn {
102
+ display: inline-flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ background: none;
106
+ border: none;
107
+ cursor: pointer;
108
+ padding: 4px 6px;
109
+ border-radius: var(--nc-radius-sm, 4px);
110
+ color: var(--nc-text);
111
+ transition: background var(--nc-transition-fast), color var(--nc-transition-fast);
112
+ line-height: 1;
113
+ }
114
+ .tb-btn:hover { background: var(--nc-bg-tertiary); }
115
+ .tb-btn.active { background: var(--nc-primary); color: #fff; }
116
+
117
+ .tb-sep {
118
+ width: 1px;
119
+ height: 18px;
120
+ background: var(--nc-border);
121
+ margin: 0 4px;
122
+ flex-shrink: 0;
123
+ }
124
+
125
+ .editable {
126
+ padding: var(--nc-spacing-md);
127
+ min-height: ${minHeight};
128
+ max-height: ${maxHeight};
129
+ overflow-y: auto;
130
+ outline: none;
131
+ font-size: var(--nc-font-size-base);
132
+ line-height: var(--nc-line-height-relaxed, 1.7);
133
+ color: var(--nc-text);
134
+ word-break: break-word;
135
+ }
136
+
137
+ .editable:empty::before {
138
+ content: attr(data-placeholder);
139
+ color: var(--nc-text-muted);
140
+ pointer-events: none;
141
+ }
142
+
143
+ /* Content styles */
144
+ .editable h1 { font-size: 1.8em; font-weight: 700; margin: 0.5em 0 0.25em; }
145
+ .editable h2 { font-size: 1.4em; font-weight: 700; margin: 0.5em 0 0.25em; }
146
+ .editable h3 { font-size: 1.1em; font-weight: 700; margin: 0.5em 0 0.25em; }
147
+ .editable ul, .editable ol { padding-left: 1.5em; margin: 0.4em 0; }
148
+ .editable blockquote { border-left: 3px solid var(--nc-primary); padding-left: var(--nc-spacing-md); margin: 0.4em 0; color: var(--nc-text-muted); font-style: italic; }
149
+ .editable code, .editable pre { background: var(--nc-bg-tertiary); border-radius: 4px; font-family: monospace; font-size: 0.9em; padding: 1px 5px; }
150
+ .editable a { color: var(--nc-primary); text-decoration: underline; }
151
+ </style>
152
+ <div class="editor-wrap">
153
+ <div class="toolbar">${toolbarHtml}</div>
154
+ <div
155
+ class="editable"
156
+ contenteditable="${!disabled && !readonly ? 'true' : 'false'}"
157
+ data-placeholder="${placeholder}"
158
+ role="textbox"
159
+ aria-multiline="true"
160
+ aria-label="${placeholder}"
161
+ >${this._value}</div>
162
+ </div>
163
+ <input type="hidden" name="${this.getAttribute('name') || ''}" value="" />
164
+ `;
165
+ }
166
+ onMount() {
167
+ this._bindEvents();
168
+ // Sync hidden input with initial value
169
+ const hidden = this.$('input[type="hidden"]');
170
+ hidden.value = this._value;
171
+ }
172
+ _exec(cmd) {
173
+ const editable = this.$('.editable');
174
+ editable.focus();
175
+ switch (cmd) {
176
+ case 'bold':
177
+ document.execCommand('bold', false);
178
+ break;
179
+ case 'italic':
180
+ document.execCommand('italic', false);
181
+ break;
182
+ case 'underline':
183
+ document.execCommand('underline', false);
184
+ break;
185
+ case 'strike':
186
+ document.execCommand('strikeThrough', false);
187
+ break;
188
+ case 'h1':
189
+ document.execCommand('formatBlock', false, 'h1');
190
+ break;
191
+ case 'h2':
192
+ document.execCommand('formatBlock', false, 'h2');
193
+ break;
194
+ case 'h3':
195
+ document.execCommand('formatBlock', false, 'h3');
196
+ break;
197
+ case 'ul':
198
+ document.execCommand('insertUnorderedList', false);
199
+ break;
200
+ case 'ol':
201
+ document.execCommand('insertOrderedList', false);
202
+ break;
203
+ case 'blockquote':
204
+ document.execCommand('formatBlock', false, 'blockquote');
205
+ break;
206
+ case 'code':
207
+ document.execCommand('formatBlock', false, 'pre');
208
+ break;
209
+ case 'align-left':
210
+ document.execCommand('justifyLeft', false);
211
+ break;
212
+ case 'align-center':
213
+ document.execCommand('justifyCenter', false);
214
+ break;
215
+ case 'align-right':
216
+ document.execCommand('justifyRight', false);
217
+ break;
218
+ case 'clear':
219
+ document.execCommand('removeFormat', false);
220
+ break;
221
+ case 'link': {
222
+ const url = prompt('Enter URL:');
223
+ if (url)
224
+ document.execCommand('createLink', false, url);
225
+ break;
226
+ }
227
+ }
228
+ this._onContentChange();
229
+ this._updateToolbarState();
230
+ }
231
+ _onContentChange() {
232
+ const editable = this.$('.editable');
233
+ const html = editable.innerHTML;
234
+ this._value = html;
235
+ const hidden = this.$('input[type="hidden"]');
236
+ if (hidden)
237
+ hidden.value = html;
238
+ const name = this.getAttribute('name') || '';
239
+ this.dispatchEvent(new CustomEvent('input', {
240
+ bubbles: true, composed: true,
241
+ detail: { value: html, name }
242
+ }));
243
+ }
244
+ _updateToolbarState() {
245
+ this.shadowRoot.querySelectorAll('.tb-btn').forEach(btn => {
246
+ const cmd = btn.dataset.cmd ?? '';
247
+ let active = false;
248
+ switch (cmd) {
249
+ case 'bold':
250
+ active = document.queryCommandState('bold');
251
+ break;
252
+ case 'italic':
253
+ active = document.queryCommandState('italic');
254
+ break;
255
+ case 'underline':
256
+ active = document.queryCommandState('underline');
257
+ break;
258
+ case 'strike':
259
+ active = document.queryCommandState('strikeThrough');
260
+ break;
261
+ }
262
+ btn.classList.toggle('active', active);
263
+ });
264
+ }
265
+ _bindEvents() {
266
+ const toolbar = this.$('.toolbar');
267
+ const editable = this.$('.editable');
268
+ toolbar.addEventListener('mousedown', (e) => {
269
+ // Prevent losing selection when clicking toolbar buttons
270
+ e.preventDefault();
271
+ });
272
+ toolbar.addEventListener('click', (e) => {
273
+ const btn = e.target.closest('[data-cmd]');
274
+ if (btn)
275
+ this._exec(btn.dataset.cmd);
276
+ });
277
+ editable.addEventListener('input', () => this._onContentChange());
278
+ editable.addEventListener('blur', () => {
279
+ this.dispatchEvent(new CustomEvent('change', {
280
+ bubbles: true, composed: true,
281
+ detail: { value: this._value, name: this.getAttribute('name') || '' }
282
+ }));
283
+ });
284
+ editable.addEventListener('keyup', () => this._updateToolbarState());
285
+ editable.addEventListener('mouseup', () => this._updateToolbarState());
286
+ }
287
+ getValue() { return this._value; }
288
+ setValue(html) {
289
+ this._value = html;
290
+ const editable = this.$('.editable');
291
+ if (editable)
292
+ editable.innerHTML = html;
293
+ const hidden = this.$('input[type="hidden"]');
294
+ if (hidden)
295
+ hidden.value = html;
296
+ }
297
+ attributeChangedCallback(name, oldValue, newValue) {
298
+ if (oldValue === newValue)
299
+ return;
300
+ if (name === 'value' && this._mounted) {
301
+ this.setValue(newValue || '');
302
+ return;
303
+ }
304
+ if (this._mounted) {
305
+ this.render();
306
+ this._bindEvents();
307
+ }
308
+ }
309
+ }
310
+ defineComponent('nc-rich-text', NcRichText);
@@ -0,0 +1,28 @@
1
+ /**
2
+ * NcScrollTop Component - floating "back to top" button
3
+ *
4
+ * Appends itself to document.body so it always sits over all content.
5
+ * Becomes visible after the user scrolls past `threshold` px.
6
+ *
7
+ * Attributes:
8
+ * threshold - scroll distance in px before appearing (default: 300)
9
+ * position - 'bottom-right'(default)|'bottom-left'|'bottom-center'
10
+ * smooth - boolean - use smooth scrolling (default: true)
11
+ * label - accessible aria-label (default: 'Back to top')
12
+ * offset - distance from screen edge in px (default: 24)
13
+ * target - optional CSS selector for the scroll container (default: window)
14
+ *
15
+ * Usage:
16
+ * <nc-scroll-top></nc-scroll-top>
17
+ */
18
+ import { Component } from '../core/component.js';
19
+ export declare class NcScrollTop extends Component {
20
+ static useShadowDOM: boolean;
21
+ private _visible;
22
+ private _removeScroll;
23
+ private _scrollTarget;
24
+ template(): string;
25
+ onMount(): void;
26
+ private _scrollTop;
27
+ onUnmount(): void;
28
+ }