@semantic-components/editor 0.3.0 → 0.61.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 (45) hide show
  1. package/README.md +11 -4
  2. package/eslint.config.mjs +48 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +10 -134
  5. package/project.json +28 -0
  6. package/src/index.ts +1 -0
  7. package/src/lib/components/editor/README.md +354 -0
  8. package/src/lib/components/editor/editor-align-center-button.ts +45 -0
  9. package/src/lib/components/editor/editor-align-justify-button.ts +45 -0
  10. package/src/lib/components/editor/editor-align-left-button.ts +44 -0
  11. package/src/lib/components/editor/editor-align-right-button.ts +44 -0
  12. package/src/lib/components/editor/editor-blockquote-button.ts +44 -0
  13. package/src/lib/components/editor/editor-bold-button.ts +44 -0
  14. package/src/lib/components/editor/editor-bullet-list-button.ts +44 -0
  15. package/src/lib/components/editor/editor-char-count.ts +42 -0
  16. package/src/lib/components/editor/editor-clear-formatting-button.ts +42 -0
  17. package/src/lib/components/editor/editor-code-button.ts +52 -0
  18. package/src/lib/components/editor/editor-content.ts +107 -0
  19. package/src/lib/components/editor/editor-count.ts +28 -0
  20. package/src/lib/components/editor/editor-footer.ts +30 -0
  21. package/src/lib/components/editor/editor-header.ts +27 -0
  22. package/src/lib/components/editor/editor-heading-select.ts +48 -0
  23. package/src/lib/components/editor/editor-horizontal-rule-button.ts +42 -0
  24. package/src/lib/components/editor/editor-italic-button.ts +44 -0
  25. package/src/lib/components/editor/editor-link-button.ts +58 -0
  26. package/src/lib/components/editor/editor-numbered-list-button.ts +44 -0
  27. package/src/lib/components/editor/editor-redo-button.ts +42 -0
  28. package/src/lib/components/editor/editor-separator.ts +25 -0
  29. package/src/lib/components/editor/editor-strikethrough-button.ts +44 -0
  30. package/src/lib/components/editor/editor-toolbar-group.ts +27 -0
  31. package/src/lib/components/editor/editor-toolbar.ts +32 -0
  32. package/src/lib/components/editor/editor-underline-button.ts +44 -0
  33. package/src/lib/components/editor/editor-undo-button.ts +42 -0
  34. package/src/lib/components/editor/editor-word-count.ts +43 -0
  35. package/src/lib/components/editor/editor.ts +211 -0
  36. package/src/lib/components/editor/index.ts +45 -0
  37. package/src/lib/components/index.ts +1 -0
  38. package/src/lib/styles/editor.css +94 -0
  39. package/src/lib/styles/index.css +1 -0
  40. package/tsconfig.json +28 -0
  41. package/tsconfig.lib.json +12 -0
  42. package/tsconfig.lib.prod.json +7 -0
  43. package/fesm2022/semantic-components-editor.mjs +0 -4721
  44. package/fesm2022/semantic-components-editor.mjs.map +0 -1
  45. package/index.d.ts +0 -385
package/README.md CHANGED
@@ -1,7 +1,14 @@
1
- # editor
1
+ # @semantic-components/editor
2
2
 
3
- This library was generated with [Nx](https://nx.dev).
3
+ Rich text editor components built on top of Tiptap for the Semantic Components library.
4
4
 
5
- ## Running unit tests
5
+ ## Contents
6
6
 
7
- Run `nx test editor` to execute the unit tests.
7
+ - **Components**: Editor, toolbar, formatting buttons (bold, italic, underline, strikethrough), alignment, lists, headings, links, code, blockquote, horizontal rule, undo/redo, word/char count
8
+ - **Styles**: ProseMirror editor styles
9
+
10
+ ## Usage
11
+
12
+ ```ts
13
+ import { ScEditor, ScEditorContent, ScEditorToolbar, ScEditorBoldButton } from '@semantic-components/editor';
14
+ ```
@@ -0,0 +1,48 @@
1
+ import nx from '@nx/eslint-plugin';
2
+ import baseConfig from '../../eslint.config.mjs';
3
+
4
+ export default [
5
+ ...baseConfig,
6
+ {
7
+ files: ['**/*.json'],
8
+ rules: {
9
+ '@nx/dependency-checks': [
10
+ 'error',
11
+ {
12
+ ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
13
+ },
14
+ ],
15
+ },
16
+ languageOptions: {
17
+ parser: await import('jsonc-eslint-parser'),
18
+ },
19
+ },
20
+ ...nx.configs['flat/angular'],
21
+ ...nx.configs['flat/angular-template'],
22
+ {
23
+ files: ['**/*.ts'],
24
+ rules: {
25
+ '@angular-eslint/directive-selector': [
26
+ 'error',
27
+ {
28
+ type: 'attribute',
29
+ prefix: 'sc',
30
+ style: 'camelCase',
31
+ },
32
+ ],
33
+ '@angular-eslint/component-selector': [
34
+ 'error',
35
+ {
36
+ type: 'element',
37
+ prefix: 'sc',
38
+ style: 'kebab-case',
39
+ },
40
+ ],
41
+ },
42
+ },
43
+ {
44
+ files: ['**/*.html'],
45
+ // Override or add rules here
46
+ rules: {},
47
+ },
48
+ ];
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/libs/editor",
4
+ "lib": {
5
+ "entryFile": "src/index.ts"
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,137 +1,13 @@
1
1
  {
2
2
  "name": "@semantic-components/editor",
3
- "version": "0.3.0",
4
- "private": false,
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
- "author": "Khalil LAGRIDA",
9
- "license": "MIT",
3
+ "version": "0.61.0",
10
4
  "peerDependencies": {
11
- "@angular/core": ">=19.0.0",
12
- "@semantic-icons/lucide-icons": ">=0.45.0",
13
- "@angular/cdk": ">=19.0.0",
14
- "@semantic-components/utils": "0.45.0",
15
- "@angular/forms": ">=19.0.0",
16
- "@tiptap/core": "^2.12.0",
17
- "@tiptap/extension-document": "^2.12.0",
18
- "@tiptap/extension-heading": "^2.12.0",
19
- "@tiptap/extension-paragraph": "^2.12.0",
20
- "@tiptap/extension-text": "^2.12.0",
21
- "@tiptap/extension-highlight": "^2.12.0",
22
- "@tiptap/extension-text-style": "^2.12.0",
23
- "@tiptap/extension-color": "^2.12.0",
24
- "@tiptap/extension-font-family": "^2.12.0",
25
- "@tiptap/extension-underline": "^2.12.0",
26
- "@tiptap/extension-image": "^2.12.0",
27
- "@tiptap/extension-youtube": "^2.12.0",
28
- "@tiptap/extension-link": "^2.12.0",
29
- "@tiptap/extension-text-align": "^2.12.0",
30
- "@tiptap/extension-list-item": "^2.12.0",
31
- "@tiptap/extension-bullet-list": "^2.12.0",
32
- "@tiptap/extension-ordered-list": "^2.12.0",
33
- "@tiptap/extension-strike": "^2.12.0",
34
- "@tiptap/extension-italic": "^2.12.0",
35
- "@tiptap/extension-bold": "^2.12.0",
36
- "@tiptap/extension-horizontal-rule": "^2.12.0",
37
- "@tiptap/extension-blockquote": "^2.12.0",
38
- "@tiptap/extension-code": "^2.12.0",
39
- "@tiptap/extension-history": "^2.12.0",
40
- "@tiptap/extension-table-header": "^2.12.0",
41
- "@tiptap/extension-table-row": "^2.12.0",
42
- "@tiptap/extension-table-cell": "^2.12.0",
43
- "@tiptap/extension-table": "^2.12.0",
44
- "@semantic-components/ui": "0.56.0"
45
- },
46
- "peerDependenciesMeta": {
47
- "@tiptap/extension-blockquote": {
48
- "optional": true
49
- },
50
- "@tiptap/extension-bold": {
51
- "optional": true
52
- },
53
- "@tiptap/extension-bullet-list": {
54
- "optional": true
55
- },
56
- "@tiptap/extension-code": {
57
- "optional": true
58
- },
59
- "@tiptap/extension-color": {
60
- "optional": true
61
- },
62
- "@tiptap/extension-font-family": {
63
- "optional": true
64
- },
65
- "@tiptap/extension-highlight": {
66
- "optional": true
67
- },
68
- "@tiptap/extension-history": {
69
- "optional": true
70
- },
71
- "@tiptap/extension-horizontal-rule": {
72
- "optional": true
73
- },
74
- "@tiptap/extension-image": {
75
- "optional": true
76
- },
77
- "@tiptap/extension-italic": {
78
- "optional": true
79
- },
80
- "@tiptap/extension-link": {
81
- "optional": true
82
- },
83
- "@tiptap/extension-list-item": {
84
- "optional": true
85
- },
86
- "@tiptap/extension-ordered-list": {
87
- "optional": true
88
- },
89
- "@tiptap/extension-text-style": {
90
- "optional": true
91
- },
92
- "@tiptap/extension-strike": {
93
- "optional": true
94
- },
95
- "@tiptap/extension-text-align": {
96
- "optional": true
97
- },
98
- "@tiptap/extension-underline": {
99
- "optional": true
100
- },
101
- "@tiptap/extension-youtube": {
102
- "optional": true
103
- },
104
- "@tiptap/extension-table-header": {
105
- "optional": true
106
- },
107
- "@tiptap/extension-table-row": {
108
- "optional": true
109
- },
110
- "@tiptap/extension-table-cell": {
111
- "optional": true
112
- },
113
- "@tiptap/extension-table": {
114
- "optional": true
115
- }
116
- },
117
- "sideEffects": false,
118
- "repository": {
119
- "type": "git",
120
- "url": "https://github.com/khalilou88/semantic-components",
121
- "directory": "libs/editor"
122
- },
123
- "module": "fesm2022/semantic-components-editor.mjs",
124
- "typings": "index.d.ts",
125
- "exports": {
126
- "./package.json": {
127
- "default": "./package.json"
128
- },
129
- ".": {
130
- "types": "./index.d.ts",
131
- "default": "./fesm2022/semantic-components-editor.mjs"
132
- }
133
- },
134
- "dependencies": {
135
- "tslib": "^2.3.0"
136
- }
137
- }
5
+ "@angular/core": ">=21.1.0",
6
+ "@semantic-components/ui": "0.61.0",
7
+ "@tiptap/core": ">=3.18.0",
8
+ "@tiptap/extension-placeholder": ">=3.18.0",
9
+ "@tiptap/extension-text-align": ">=3.18.0",
10
+ "@tiptap/starter-kit": ">=3.18.0"
11
+ },
12
+ "sideEffects": false
13
+ }
package/project.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "editor",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/editor/src",
5
+ "prefix": "sc",
6
+ "projectType": "library",
7
+ "tags": [],
8
+ "targets": {
9
+ "build": {
10
+ "executor": "@nx/angular:ng-packagr-lite",
11
+ "outputs": ["{workspaceRoot}/dist/{projectRoot}"],
12
+ "options": {
13
+ "project": "libs/editor/ng-package.json",
14
+ "tsConfig": "libs/editor/tsconfig.lib.json"
15
+ },
16
+ "configurations": {
17
+ "production": {
18
+ "tsConfig": "libs/editor/tsconfig.lib.prod.json"
19
+ },
20
+ "development": {}
21
+ },
22
+ "defaultConfiguration": "production"
23
+ },
24
+ "lint": {
25
+ "executor": "@nx/eslint:lint"
26
+ }
27
+ }
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './lib/components';
@@ -0,0 +1,354 @@
1
+ # Editor Component
2
+
3
+ Composable WYSIWYG editor built with [Tiptap](https://tiptap.dev/), a headless rich-text editor framework powered by ProseMirror.
4
+
5
+ ## Installation
6
+
7
+ The editor component requires Tiptap packages as peer dependencies. Install them alongside `@semantic-components/ui`:
8
+
9
+ ```bash
10
+ npm install @semantic-components/ui \
11
+ @tiptap/core \
12
+ @tiptap/starter-kit \
13
+ @tiptap/extension-text-align \
14
+ @tiptap/extension-placeholder
15
+ ```
16
+
17
+ Or if you already have `@semantic-components/ui` installed:
18
+
19
+ ```bash
20
+ npm install @tiptap/core @tiptap/starter-kit @tiptap/extension-text-align @tiptap/extension-placeholder
21
+ ```
22
+
23
+ **Note**: Link and Underline extensions are included in `@tiptap/starter-kit` v3, so no need to install them separately.
24
+
25
+ ## Features
26
+
27
+ - **Rich Text Editing**: Bold, italic, underline, strikethrough, headings, lists, blockquotes, code, links, and more
28
+ - **Keyboard Shortcuts**: Built-in shortcuts for all formatting commands (Ctrl+B, Ctrl+I, Ctrl+U, Ctrl+Z, Ctrl+Y, etc.)
29
+ - **Customizable Toolbar**: Composable architecture allows full control over toolbar layout
30
+ - **Two-Way Binding**: Seamless synchronization with Angular signals
31
+ - **Accessibility**: Full keyboard navigation and ARIA support
32
+ - **Extensible**: Built on Tiptap, allowing custom extensions and commands
33
+
34
+ ## Implementation
35
+
36
+ The editor is powered by [Tiptap](https://tiptap.dev/), a headless rich-text editor framework built on ProseMirror. This provides:
37
+
38
+ - **Robust editing**: Industry-standard editing engine
39
+ - **Extensibility**: Add custom extensions and commands
40
+ - **Keyboard shortcuts**: Built-in shortcuts for all formatting
41
+ - **Better browser support**: Works consistently across modern browsers
42
+ - **Future-ready**: Foundation for collaboration, mentions, slash commands, etc.
43
+
44
+ ### Extensions Included
45
+
46
+ The editor comes pre-configured with these Tiptap extensions:
47
+
48
+ - **StarterKit**: Bold, Italic, Strike, Underline, Code, Heading, Lists, Blockquote, History, HardBreak, Paragraph, Text, Link
49
+ - Link configured with: `openOnClick: false`, `target="_blank"`, `rel="noopener noreferrer"`
50
+ - **TextAlign**: Text alignment (left, center, right, justify)
51
+ - **Placeholder**: Customizable placeholder text
52
+
53
+ ### Bundle Size
54
+
55
+ - **Total**: ~30 KB (minified)
56
+ - **Gzipped**: ~10 KB
57
+
58
+ This is an acceptable trade-off for the significant improvements in reliability, extensibility, and developer experience.
59
+
60
+ ## Basic Usage
61
+
62
+ ```typescript
63
+ import { Component, signal } from '@angular/core';
64
+ import { ScEditor, ScEditorContent, ScEditorToolbar, ScEditorToolbarGroup, ScEditorBoldButton, ScEditorItalicButton, ScEditorUnderlineButton, ScEditorFooter, ScEditorCount, ScEditorWordCount, ScEditorCharCount } from '@semantic-components/ui-lab';
65
+
66
+ @Component({
67
+ selector: 'app-example',
68
+ imports: [ScEditor, ScEditorContent, ScEditorToolbar, ScEditorToolbarGroup, ScEditorBoldButton, ScEditorItalicButton, ScEditorUnderlineButton, ScEditorFooter, ScEditorCount, ScEditorWordCount, ScEditorCharCount],
69
+ template: `
70
+ <div sc-editor class="border rounded-lg overflow-hidden">
71
+ <div sc-editor-toolbar>
72
+ <div sc-editor-toolbar-group>
73
+ <button sc-editor-bold>Bold</button>
74
+ <button sc-editor-italic>Italic</button>
75
+ <button sc-editor-underline>Underline</button>
76
+ </div>
77
+ </div>
78
+
79
+ <div sc-editor-content [(value)]="content" placeholder="Start typing..."></div>
80
+
81
+ <div sc-editor-footer>
82
+ <div sc-editor-count>
83
+ <span sc-editor-word-count></span>
84
+ <span sc-editor-char-count></span>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ `,
89
+ })
90
+ export class ExampleComponent {
91
+ readonly content = signal('');
92
+ }
93
+ ```
94
+
95
+ ## API
96
+
97
+ ### ScEditor (Directive)
98
+
99
+ Root directive that manages editor state and provides context to child components.
100
+
101
+ **Selector**: `[sc-editor]`
102
+
103
+ **Inputs**:
104
+
105
+ - `disabled: boolean` - Disables the editor
106
+ - `readonly: boolean` - Makes the editor read-only
107
+
108
+ **Signals** (accessed via `inject(SC_EDITOR)`):
109
+
110
+ - `isBold()` - Whether bold is active
111
+ - `isItalic()` - Whether italic is active
112
+ - `isUnderline()` - Whether underline is active
113
+ - `isStrikethrough()` - Whether strikethrough is active
114
+ - `isOrderedList()` - Whether ordered list is active
115
+ - `isUnorderedList()` - Whether unordered list is active
116
+ - `isBlockquote()` - Whether blockquote is active
117
+ - `alignment()` - Current text alignment ('left' | 'center' | 'right' | 'justify')
118
+ - `currentHeading()` - Current heading level ('p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6')
119
+ - `canUndo()` - Whether undo is available
120
+ - `canRedo()` - Whether redo is available
121
+ - `editorInstance()` - The Tiptap Editor instance (for advanced usage)
122
+
123
+ **Methods**:
124
+
125
+ - `execCommand(command: string, value?: string)` - Execute a formatting command
126
+ - `updateToolbarState()` - Update toolbar button states (called automatically)
127
+ - `initializeEditor(element: HTMLElement, content: string, placeholder: string)` - Initialize Tiptap (called automatically)
128
+ - `destroyEditor()` - Cleanup Tiptap instance (called automatically)
129
+
130
+ ### ScEditorContent (Component)
131
+
132
+ Content area where text editing happens.
133
+
134
+ **Selector**: `div[sc-editor-content]`
135
+
136
+ **Inputs**:
137
+
138
+ - `value: string` - Two-way bindable HTML content (use with `[(value)]`)
139
+ - `placeholder: string` - Placeholder text (default: "Start typing...")
140
+ - `ariaLabel: string` - ARIA label (default: "Rich text editor")
141
+ - `class: string` - Additional CSS classes (use Tailwind utilities for sizing, e.g., `min-h-[300px] max-h-[500px]`)
142
+
143
+ **Outputs**:
144
+
145
+ - `focus` - Emitted when editor gains focus
146
+ - `blur` - Emitted when editor loses focus
147
+
148
+ ### Toolbar Components
149
+
150
+ All toolbar buttons inject `SC_EDITOR` and call the appropriate commands.
151
+
152
+ **Available Buttons**:
153
+
154
+ - `ScEditorBoldButton` - Toggle bold
155
+ - `ScEditorItalicButton` - Toggle italic
156
+ - `ScEditorUnderlineButton` - Toggle underline
157
+ - `ScEditorStrikethroughButton` - Toggle strikethrough
158
+ - `ScEditorCodeButton` - Toggle inline code
159
+ - `ScEditorLinkButton` - Insert/edit link (prompts for URL)
160
+ - `ScEditorBulletListButton` - Toggle bullet list
161
+ - `ScEditorNumberedListButton` - Toggle numbered list
162
+ - `ScEditorBlockquoteButton` - Toggle blockquote
163
+ - `ScEditorAlignLeftButton` - Align left
164
+ - `ScEditorAlignCenterButton` - Align center
165
+ - `ScEditorAlignRightButton` - Align right
166
+ - `ScEditorAlignJustifyButton` - Align justify
167
+ - `ScEditorUndoButton` - Undo last action
168
+ - `ScEditorRedoButton` - Redo last action
169
+ - `ScEditorClearFormattingButton` - Remove all formatting
170
+
171
+ **Containers**:
172
+
173
+ - `ScEditorToolbar` - Toolbar container
174
+ - `ScEditorToolbarGroup` - Group related buttons
175
+ - `ScEditorSeparator` - Visual separator between groups
176
+ - `ScEditorFooter` - Footer container
177
+ - `ScEditorHeader` - Header container
178
+
179
+ **Utilities**:
180
+
181
+ - `ScEditorCount` - Container for count components (uses `<ng-content>`, allows custom layout)
182
+ - `ScEditorWordCount` - Display word count (e.g., "42 words")
183
+ - `ScEditorCharCount` - Display character count (e.g., "256 characters")
184
+ - `ScEditorHeadingSelect` - Dropdown to select heading level
185
+
186
+ ## Keyboard Shortcuts
187
+
188
+ All standard keyboard shortcuts are handled by Tiptap:
189
+
190
+ - **Ctrl+B** / **Cmd+B** - Bold
191
+ - **Ctrl+I** / **Cmd+I** - Italic
192
+ - **Ctrl+U** / **Cmd+U** - Underline
193
+ - **Ctrl+Z** / **Cmd+Z** - Undo
194
+ - **Ctrl+Shift+Z** / **Cmd+Shift+Z** - Redo
195
+ - **Ctrl+Y** / **Cmd+Y** - Redo
196
+ - **Ctrl+E** / **Cmd+E** - Inline code
197
+
198
+ ## Advanced Usage
199
+
200
+ ### Accessing the Tiptap Editor Instance
201
+
202
+ For advanced use cases, you can access the underlying Tiptap editor:
203
+
204
+ ```typescript
205
+ import { inject, effect } from '@angular/core';
206
+ import { SC_EDITOR } from '@semantic-components/ui-lab';
207
+
208
+ export class MyComponent {
209
+ readonly editor = inject(SC_EDITOR);
210
+
211
+ constructor() {
212
+ effect(() => {
213
+ const instance = this.editor.editorInstance();
214
+ if (instance) {
215
+ // Access Tiptap API
216
+ console.log(instance.getJSON()); // Get content as JSON
217
+ console.log(instance.getHTML()); // Get content as HTML
218
+ console.log(instance.getText()); // Get plain text
219
+
220
+ // Execute custom commands
221
+ instance.chain().focus().toggleBold().run();
222
+ }
223
+ });
224
+ }
225
+ }
226
+ ```
227
+
228
+ ### Custom Commands
229
+
230
+ You can execute any Tiptap command via the editor instance:
231
+
232
+ ```typescript
233
+ this.editor.editorInstance()?.chain().focus().setHeading({ level: 2 }).insertContent('Hello world!').run();
234
+ ```
235
+
236
+ ## Migration from execCommand
237
+
238
+ Previous versions used the deprecated `document.execCommand()` API. The current version uses Tiptap internally while maintaining the same external API for backwards compatibility.
239
+
240
+ **Breaking Changes**: None! The external API remains identical.
241
+
242
+ **Advanced Users**: If you directly accessed `contentElement()` for DOM manipulation, you should now use `editorInstance()` to interact with the editor via Tiptap's API.
243
+
244
+ ## Future Enhancements
245
+
246
+ Built on Tiptap, these features can be easily added in the future:
247
+
248
+ 1. **Collaboration** - Real-time co-editing with `@tiptap/extension-collaboration`
249
+ 2. **Mentions** - @-mentions with autocomplete
250
+ 3. **Slash Commands** - `/` for quick formatting
251
+ 4. **Tables** - Rich table support
252
+ 5. **Images** - Drag-and-drop image upload
253
+ 6. **Custom Extensions** - Domain-specific formatting
254
+ 7. **Markdown Support** - Input/output markdown
255
+ 8. **Character Limits** - Built-in extension for content limits
256
+
257
+ ## Styling
258
+
259
+ The editor uses Tailwind CSS with CSS variables for theming. All styles respect the current theme (light/dark mode) and use OKLCH color format.
260
+
261
+ **CSS Variables Used**:
262
+
263
+ - `--border` - Border color (blockquote, hr)
264
+ - `--muted` - Muted background (code, pre)
265
+ - `--muted-foreground` - Muted text (placeholder, blockquote)
266
+ - `--primary` - Primary color (links)
267
+
268
+ Colors are defined in OKLCH format and automatically adapt to light/dark themes.
269
+
270
+ **Customization**:
271
+
272
+ ```html
273
+ <!-- Custom font and text size -->
274
+ <div sc-editor-content class="font-serif text-lg"></div>
275
+
276
+ <!-- Custom height constraints -->
277
+ <div sc-editor-content class="min-h-[300px] max-h-[600px]"></div>
278
+
279
+ <!-- Remove default padding -->
280
+ <div sc-editor-content class="p-0"></div>
281
+
282
+ <!-- Default: Both counts with standard layout -->
283
+ <div sc-editor-footer>
284
+ <div sc-editor-count>
285
+ <span sc-editor-word-count></span>
286
+ <span sc-editor-char-count></span>
287
+ </div>
288
+ </div>
289
+
290
+ <!-- Only word count -->
291
+ <div sc-editor-footer>
292
+ <div sc-editor-count>
293
+ <span sc-editor-word-count></span>
294
+ </div>
295
+ </div>
296
+
297
+ <!-- Only character count -->
298
+ <div sc-editor-footer>
299
+ <div sc-editor-count>
300
+ <span sc-editor-char-count></span>
301
+ </div>
302
+ </div>
303
+
304
+ <!-- Custom separator and styling -->
305
+ <div sc-editor-footer>
306
+ <div sc-editor-count>
307
+ <span sc-editor-word-count class="text-blue-600"></span>
308
+ <span class="text-muted-foreground">•</span>
309
+ <span sc-editor-char-count class="text-green-600"></span>
310
+ </div>
311
+ </div>
312
+
313
+ <!-- Footer with additional content -->
314
+ <div sc-editor-footer>
315
+ <button class="text-sm text-primary hover:underline">Save draft</button>
316
+ <div sc-editor-count>
317
+ <span sc-editor-word-count></span>
318
+ <span sc-editor-char-count></span>
319
+ </div>
320
+ </div>
321
+
322
+ <!-- Without using ScEditorCount container -->
323
+ <div sc-editor-footer class="justify-between">
324
+ <div class="flex gap-2">
325
+ <span sc-editor-word-count class="text-xs"></span>
326
+ <span sc-editor-char-count class="text-xs"></span>
327
+ </div>
328
+ <button>Submit</button>
329
+ </div>
330
+ ```
331
+
332
+ Default classes applied: `block outline-none overflow-y-auto min-h-[150px] max-h-[400px] p-4 prose prose-sm max-w-none dark:prose-invert`
333
+
334
+ ## Accessibility
335
+
336
+ The editor follows WCAG AA standards:
337
+
338
+ - Full keyboard navigation
339
+ - ARIA labels on all buttons
340
+ - Focus management
341
+ - Screen reader support
342
+ - High contrast mode support
343
+
344
+ ## Browser Support
345
+
346
+ Works in all modern browsers that support ES2015+ and ProseMirror:
347
+
348
+ - Chrome/Edge 90+
349
+ - Firefox 88+
350
+ - Safari 14+
351
+
352
+ ## License
353
+
354
+ MIT
@@ -0,0 +1,45 @@
1
+ import {
2
+ Component,
3
+ ChangeDetectionStrategy,
4
+ computed,
5
+ input,
6
+ inject,
7
+ ViewEncapsulation,
8
+ } from '@angular/core';
9
+ import { cn } from '@semantic-components/ui';
10
+ import { SC_EDITOR } from './editor';
11
+
12
+ @Component({
13
+ selector: 'button[sc-editor-align-center]',
14
+ template: `
15
+ <ng-content />
16
+ `,
17
+ host: {
18
+ 'data-slot': 'editor-align-center',
19
+ type: 'button',
20
+ '[class]': 'class()',
21
+ '[disabled]': 'editor.disabled()',
22
+ '[attr.aria-pressed]': 'editor.alignment() === "center"',
23
+ '[attr.title]': '"Align center"',
24
+ '(click)': 'onClick()',
25
+ },
26
+ encapsulation: ViewEncapsulation.None,
27
+ changeDetection: ChangeDetectionStrategy.OnPush,
28
+ })
29
+ export class ScEditorAlignCenterButton {
30
+ readonly editor = inject(SC_EDITOR);
31
+ readonly classInput = input<string>('', { alias: 'class' });
32
+
33
+ protected readonly class = computed(() =>
34
+ cn(
35
+ 'p-1.5 rounded hover:bg-accent disabled:opacity-50 [&_svg]:size-4',
36
+ this.editor.alignment() === 'center' &&
37
+ 'bg-accent text-accent-foreground',
38
+ this.classInput(),
39
+ ),
40
+ );
41
+
42
+ onClick(): void {
43
+ this.editor.execCommand('justifyCenter');
44
+ }
45
+ }