@phuong-tran-redoc/document-engine-angular 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phuong-tran-redoc/document-engine-angular",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Realestatedoc (Redoc)",
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "tslib": "^2.3.0",
37
37
  "@floating-ui/dom": "^1.7.4",
38
- "@phuong-tran-redoc/document-engine-core": "0.1.1"
38
+ "@phuong-tran-redoc/document-engine-core": "0.1.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "rxjs": "^7.5.0",
@@ -75,6 +75,12 @@
75
75
  "./styles": {
76
76
  "sass": "./src/lib/styles/index.scss"
77
77
  },
78
+ "./styles/editor-content": {
79
+ "sass": "./src/lib/styles/editor-content.scss"
80
+ },
81
+ "./styles/editor-interaction": {
82
+ "sass": "./src/lib/styles/editor-interaction.scss"
83
+ },
78
84
  "./package.json": {
79
85
  "default": "./package.json"
80
86
  },
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Opt-in editor CONTENT prose theme
3
+ * `@phuong-tran-redoc/document-engine-angular/styles/editor-content`
4
+ *
5
+ * Structural correctness for the editor's *rendered content*: heading scale,
6
+ * bullet + ordered lists (incl. the engine's `data-list-style-type` counters),
7
+ * blockquote, inline code, hr, img, and block rhythm.
8
+ *
9
+ * NOT auto-imposed — the main `/styles` barrel is chrome-only, so a consumer
10
+ * opts in by importing THIS subpath explicitly. Token-driven: every colour /
11
+ * dimension resolves from a CSS custom property with a sensible fallback, so a
12
+ * consumer themes it purely by remapping vars (no rule-body overrides needed).
13
+ *
14
+ * Scoped to `.tiptap-editor` (the class the editor directive sets on its host),
15
+ * so importing it can never leak prose onto unrelated app content.
16
+ */
17
+
18
+ .tiptap-editor {
19
+ /* --- Block rhythm --- */
20
+ p {
21
+ margin: 0.5rem 0;
22
+ }
23
+
24
+ p:first-of-type {
25
+ margin-top: 0;
26
+ }
27
+
28
+ p:last-of-type {
29
+ margin-bottom: 0;
30
+ }
31
+
32
+ /* --- Headings (structural floor) ---
33
+ * The engine emits class-less <h1>–<h6> and the host preflight strips their
34
+ * size, so the lib restores a token-overridable scale. Remap `--de-prose-h*`
35
+ * (size) and `--foreground` (colour) to theme. */
36
+ h1,
37
+ h2,
38
+ h3,
39
+ h4,
40
+ h5,
41
+ h6 {
42
+ font-weight: 600; // font-semibold
43
+ line-height: 1.25;
44
+ margin: 1.25rem 0 0.5rem;
45
+ color: var(--foreground, hsl(222.2 84% 4.9%));
46
+
47
+ &:first-child {
48
+ margin-top: 0;
49
+ }
50
+ }
51
+
52
+ h1 {
53
+ font-size: var(--de-prose-h1, 2rem);
54
+ }
55
+
56
+ h2 {
57
+ font-size: var(--de-prose-h2, 1.5rem);
58
+ }
59
+
60
+ h3 {
61
+ font-size: var(--de-prose-h3, 1.25rem);
62
+ }
63
+
64
+ h4 {
65
+ font-size: var(--de-prose-h4, 1.125rem);
66
+ }
67
+
68
+ h5 {
69
+ font-size: var(--de-prose-h5, 1rem);
70
+ }
71
+
72
+ h6 {
73
+ font-size: var(--de-prose-h6, 0.875rem);
74
+ }
75
+
76
+ /* --- Blockquote --- (ported from `@apply border-l-4 border-border pl-4`) */
77
+ blockquote {
78
+ border-left: 4px solid var(--border, hsl(214.3 31.8% 91.4%));
79
+ padding-left: 1rem; // pl-4
80
+ }
81
+
82
+ /* --- Horizontal rule --- (ported from `@apply border-t border-foreground`) */
83
+ hr {
84
+ border: 0;
85
+ border-top: 1px solid var(--foreground, hsl(222.2 84% 4.9%));
86
+ }
87
+
88
+ /* --- Inline code --- */
89
+ code {
90
+ background-color: var(--secondary, hsl(210 40% 96.1%));
91
+ color: var(--secondary-foreground, hsl(222.2 47.4% 11.2%));
92
+ border-radius: 0.4rem;
93
+ font-size: 0.85rem;
94
+ padding: 0.25em 0.3em;
95
+ }
96
+
97
+ /* --- Image --- */
98
+ img {
99
+ display: block;
100
+ height: auto;
101
+ width: 100%;
102
+ max-width: 100%;
103
+ margin: 1rem 0;
104
+ }
105
+
106
+ /* --- Bullet lists (structural floor) ---
107
+ * The host preflight kills `list-style`; the engine relies on the theme to
108
+ * restore markers. */
109
+ ul {
110
+ list-style: disc;
111
+ padding-left: 1.5rem;
112
+ margin: 0.5rem 0;
113
+ }
114
+
115
+ ul ul {
116
+ list-style: circle;
117
+ }
118
+
119
+ ul ul ul {
120
+ list-style: square;
121
+ }
122
+
123
+ ul li p {
124
+ margin: 0;
125
+ }
126
+
127
+ /* --- Ordered lists: engine-coupled counters ---
128
+ * The engine resets `list-style` and paints markers via CSS counters keyed off
129
+ * the `data-list-style-type` attribute (decimal / lower-alpha-dot /
130
+ * lower-alpha-parens / lower-roman-parens). A consumer cannot derive this —
131
+ * the lib owns it. */
132
+ ol[data-list-style-type] {
133
+ counter-reset: list-counter;
134
+ list-style: none;
135
+ padding-left: 3rem;
136
+ margin: 0.5rem 0;
137
+ }
138
+
139
+ ol[data-list-style-type] > li {
140
+ position: relative;
141
+ display: block;
142
+
143
+ &:not(:first-child) {
144
+ margin-top: 0.25rem;
145
+ }
146
+ }
147
+
148
+ ol[data-list-style-type] > li::before {
149
+ position: absolute;
150
+ left: -2rem;
151
+ width: 2rem;
152
+ display: inline-block;
153
+ text-align: left;
154
+ font-variant-numeric: tabular-nums;
155
+ font-weight: normal;
156
+ }
157
+
158
+ ol p {
159
+ margin: 0;
160
+ }
161
+
162
+ /* Default decimal style (1., 2., 3.) */
163
+ ol[data-list-style-type='decimal'] > li::before {
164
+ counter-increment: list-counter;
165
+ content: counter(list-counter) '.';
166
+ }
167
+
168
+ /* Lower alpha with dot style (a., b., c.) */
169
+ ol[data-list-style-type='lower-alpha-dot'] > li::before {
170
+ counter-increment: list-counter;
171
+ content: counter(list-counter, lower-alpha) '.';
172
+ }
173
+
174
+ /* Lower alpha with parentheses style ((a), (b), (c)) */
175
+ ol[data-list-style-type='lower-alpha-parens'] > li::before {
176
+ counter-increment: list-counter;
177
+ content: '(' counter(list-counter, lower-alpha) ')';
178
+ }
179
+
180
+ /* Lower roman with parentheses style ((i), (ii), (iii)) */
181
+ ol[data-list-style-type='lower-roman-parens'] > li::before {
182
+ counter-increment: list-counter;
183
+ content: '(' counter(list-counter, lower-roman) ')';
184
+ }
185
+
186
+ /* Nested list support */
187
+ ol[data-list-style-type] ol {
188
+ counter-reset: list-counter;
189
+ }
190
+ }
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Opt-in editor INTERACTION chrome theme
3
+ * `@phuong-tran-redoc/document-engine-angular/styles/editor-interaction`
4
+ *
5
+ * Editing-surface chrome for a consumer mounting a bare editor: node-view widget
6
+ * handles + type-around buttons, table editing (selected cell, column resize),
7
+ * dynamic-field nodes, restricted-editing regions, empty-state placeholders, and
8
+ * the selected-node outline.
9
+ *
10
+ * Separate from `editor-content` on purpose: content prose is what makes the
11
+ * document *render* correctly, this is what makes it *editable*. NOT auto-imposed
12
+ * (the main `/styles` barrel is chrome-only) — import this subpath explicitly.
13
+ * Token-driven with sensible fallbacks; scoped to `.tiptap-editor`.
14
+ *
15
+ * Ported from the demo app's private `_editor.scss`; Tailwind `@apply` utilities
16
+ * are inlined as plain CSS and the demo-only colour vars are remapped so the
17
+ * theme themes cleanly for any consumer:
18
+ * --light-blue / --light-blue-foreground → --accent / --accent-foreground
19
+ * --blue (hover, borders, handles) → --ring
20
+ * --blue (selected-node outline) → --primary
21
+ * --secondary (placeholder text) → --muted-foreground
22
+ * --green (dynamic-field accent) → --de-dynamic-field (engine custom prop)
23
+ */
24
+
25
+ .tiptap-editor {
26
+ position: relative;
27
+
28
+ .ProseMirror {
29
+ &:focus-visible {
30
+ outline: none;
31
+ }
32
+ }
33
+
34
+ /* Selected node (covers image + any selected block) */
35
+ .ProseMirror-selectednode {
36
+ outline: 3px solid var(--primary, hsl(221.2 83.2% 53.3%));
37
+ }
38
+
39
+ /* Restricted-editing region (was --light-blue / --blue) */
40
+ .editable-region {
41
+ background-color: var(--accent, hsl(210 40% 96.1%));
42
+ color: var(--accent-foreground, hsl(222.2 47.4% 11.2%));
43
+ border-bottom: 1px dashed var(--ring, hsl(221.2 83.2% 53.3%));
44
+ padding: 0 4px;
45
+ }
46
+
47
+ /* Placeholder (first line) — was --secondary; use --muted-foreground so the
48
+ * placeholder stays legible for any consumer token set */
49
+ p.is-editor-empty:first-child::before {
50
+ color: var(--muted-foreground, hsl(215.4 16.3% 46.9%));
51
+ content: attr(data-placeholder);
52
+ float: left;
53
+ height: 0;
54
+ pointer-events: none;
55
+ }
56
+
57
+ /* Placeholder (every empty line) */
58
+ .is-empty::before {
59
+ color: var(--muted-foreground, hsl(215.4 16.3% 46.9%));
60
+ content: attr(data-placeholder);
61
+ float: left;
62
+ height: 0;
63
+ pointer-events: none;
64
+ }
65
+
66
+ /* --- Node-view widget (figure wrapper) --- */
67
+ .widget {
68
+ position: relative; // relative
69
+ margin: 1rem 0; // my-4 mx-0
70
+ transition: opacity 0.2s ease-in-out;
71
+
72
+ &:hover {
73
+ outline: 3px solid var(--ring, hsl(221.2 83.2% 53.3%));
74
+ }
75
+
76
+ &.ProseMirror-selectednode {
77
+ outline: 2px solid var(--accent, hsl(210 40% 96.1%));
78
+ outline-offset: 2px;
79
+ }
80
+
81
+ &:hover .widget__selection-handle {
82
+ opacity: 1;
83
+ }
84
+
85
+ &:first-child {
86
+ margin-top: 0;
87
+ }
88
+
89
+ .ck p:first-child {
90
+ margin-top: 0;
91
+ }
92
+ }
93
+
94
+ /* Selection handle button */
95
+ .widget__selection-handle {
96
+ position: absolute;
97
+ z-index: 20;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ cursor: grab;
102
+ width: 1.5rem; // w-6
103
+ height: 1.5rem; // h-6
104
+ border-radius: 0.25rem; // rounded
105
+ border: 1px solid var(--border, hsl(214.3 31.8% 91.4%));
106
+ background-color: var(--card, hsl(0 0% 100%));
107
+ color: var(--muted-foreground, hsl(215.4 16.3% 46.9%));
108
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); // shadow-elevation-1
109
+
110
+ left: -32px;
111
+ top: 0;
112
+ opacity: 0;
113
+ transition: opacity 0.15s ease-in-out, background-color 0.15s ease-in-out;
114
+
115
+ svg {
116
+ width: 20px;
117
+ height: 20px;
118
+ }
119
+
120
+ &:hover {
121
+ opacity: 1 !important;
122
+ background-color: var(--accent, hsl(210 40% 96.1%));
123
+ color: var(--accent-foreground, hsl(222.2 47.4% 11.2%));
124
+ }
125
+
126
+ &:active {
127
+ cursor: grabbing;
128
+ }
129
+ }
130
+
131
+ /* Widget content area */
132
+ .widget-content {
133
+ position: relative;
134
+ }
135
+
136
+ /* Type-around buttons (insert paragraph before/after) */
137
+ .type-around {
138
+ opacity: 0;
139
+ pointer-events: none;
140
+ transition: opacity 0.2s ease-in-out;
141
+ height: auto;
142
+ width: auto;
143
+ position: static;
144
+ }
145
+
146
+ .widget:hover > .type-around {
147
+ opacity: 1;
148
+ pointer-events: auto;
149
+ }
150
+
151
+ .widget__type-around__button {
152
+ position: relative;
153
+ display: flex;
154
+ align-items: center;
155
+ justify-content: center;
156
+ cursor: pointer;
157
+ width: 1.5rem; // w-6
158
+ height: 1.5rem; // h-6
159
+ border-radius: 9999px; // rounded-full
160
+ background-color: var(--card, hsl(0 0% 100%));
161
+ color: var(--muted-foreground, hsl(215.4 16.3% 46.9%));
162
+ border: 2px solid var(--border, hsl(214.3 31.8% 91.4%));
163
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); // shadow-elevation-2
164
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
165
+ z-index: 20;
166
+
167
+ &:hover {
168
+ background-color: var(--accent, hsl(210 40% 96.1%));
169
+ color: var(--accent-foreground, hsl(222.2 47.4% 11.2%));
170
+ border-color: var(--accent, hsl(210 40% 96.1%));
171
+ }
172
+
173
+ &:active {
174
+ transform: scale(1.05);
175
+ }
176
+
177
+ &_before {
178
+ position: absolute;
179
+ top: -0.75rem;
180
+ left: 10%;
181
+ transform: translateX(-50%);
182
+ }
183
+
184
+ &_after {
185
+ position: absolute;
186
+ bottom: -0.75rem;
187
+ left: 90%;
188
+ transform: translateX(-50%);
189
+ }
190
+
191
+ svg {
192
+ width: 14px;
193
+ height: 14px;
194
+ transition: transform 0.2s ease;
195
+ }
196
+
197
+ &:hover svg {
198
+ transform: scale(1.1);
199
+ }
200
+ }
201
+
202
+ /* --- Table editing chrome --- */
203
+ .table-wrapper {
204
+ position: relative;
205
+ }
206
+
207
+ table {
208
+ width: 100%;
209
+ max-width: 100%;
210
+ border-collapse: collapse;
211
+ table-layout: fixed;
212
+ position: relative;
213
+
214
+ th,
215
+ td {
216
+ padding: 6px;
217
+ position: relative;
218
+ }
219
+
220
+ /* Selected cell (Tiptap/ProseMirror .selectedCell) */
221
+ .selectedCell {
222
+ position: relative;
223
+
224
+ &::after {
225
+ content: '';
226
+ position: absolute;
227
+ inset: 0;
228
+ z-index: 10;
229
+ background-color: var(--de-selected-cell, rgba(59, 130, 246, 0.12)); // bg-blue-100/50
230
+ pointer-events: none;
231
+ }
232
+ }
233
+
234
+ /* Column resize handle (ColumnResizing) */
235
+ .column-resize-handle {
236
+ position: absolute;
237
+ width: 0.25rem; // w-1
238
+ top: 0;
239
+ bottom: 0;
240
+ right: -0.125rem; // -right-0.5
241
+ background-color: var(--ring, hsl(221.2 83.2% 53.3%)); // bg-blue-300
242
+ pointer-events: none;
243
+ }
244
+ }
245
+
246
+ .resize-cursor {
247
+ cursor: col-resize;
248
+ }
249
+
250
+ /* Custom column-resizer handle */
251
+ .pm-col-resizer {
252
+ position: absolute;
253
+ top: 0;
254
+ z-index: 10; /* above table content */
255
+ display: block;
256
+ width: 4px;
257
+ background-color: var(--ring, hsl(221.2 83.2% 53.3%));
258
+ opacity: 0; /* hidden by default */
259
+ transition: opacity 0.2s;
260
+ cursor: col-resize;
261
+ height: 100%;
262
+
263
+ &:hover {
264
+ opacity: 1 !important;
265
+ }
266
+ }
267
+
268
+ /* --- Dynamic field node --- (accent was demo-only --green; exposed as the
269
+ * engine custom prop --de-dynamic-field so it stays remappable, since a shadcn
270
+ * consumer never defines --green) */
271
+ .dynamic-field {
272
+ border: 1px solid var(--de-dynamic-field, hsl(142.1 76.2% 36.3%));
273
+ cursor: default; /* feels non-editable */
274
+ user-select: none; /* prevent text selection inside */
275
+ white-space: nowrap;
276
+
277
+ &.ProseMirror-selectednode {
278
+ outline: 1px solid var(--ring, hsl(221.2 83.2% 53.3%));
279
+ }
280
+ }
281
+ }
@@ -1,22 +1,28 @@
1
1
  /**
2
- * Main styles entry point for @document-engine/document-engine-angular
2
+ * Main styles entry point for @phuong-tran-redoc/document-engine-angular
3
3
  *
4
- * Usage in client application:
4
+ * This barrel ships CHROME ONLY — toolbar, buttons, select, color-picker,
5
+ * the document-editor wrapper, inputs, etc. It never styles the editor's
6
+ * rendered content, so importing it can never impose a look on your document.
5
7
  *
6
- * Option 1: Import default styles (recommended)
7
- * In your main styles.scss or styles.css:
8
- * @import '@document-engine/document-engine-angular/styles';
8
+ * Usage in a client application:
9
9
  *
10
- * Option 2: Import individual style files
11
- * @import '@document-engine/document-engine-angular/styles/button';
12
- * @import '@document-engine/document-engine-angular/styles/icon';
13
- * @import '@document-engine/document-engine-angular/styles/toolbar';
14
- * @import '@document-engine/document-engine-angular/styles/document-editor';
15
- * @import '@document-engine/document-engine-angular/styles/color-picker';
10
+ * // Chrome (recommended baseline)
11
+ * @use '@phuong-tran-redoc/document-engine-angular/styles';
16
12
  *
17
- * Option 3: Provide custom styles
18
- * Styles use class and element selectors (document-engine-*, .document-engine-*),
19
- * so you can override them with your own CSS without importing these files.
13
+ * Opt-in content / interaction themes (NOT included in this barrel — import
14
+ * whichever you want, then theme by remapping their CSS custom properties):
15
+ *
16
+ * // Content prose: heading scale, lists (incl. data-list-style-type counters),
17
+ * // blockquote, code, hr, img, block rhythm
18
+ * @use '@phuong-tran-redoc/document-engine-angular/styles/editor-content';
19
+ *
20
+ * // Editing-surface chrome: node-view widget handles, table editing,
21
+ * // dynamic fields, placeholders, selected-node outline
22
+ * @use '@phuong-tran-redoc/document-engine-angular/styles/editor-interaction';
23
+ *
24
+ * All styles use class/element selectors and CSS-variable-driven values, so you
25
+ * can also override them with your own CSS without importing these files.
20
26
  */
21
27
 
22
28
  @forward 'icon';
@@ -56,3 +56,18 @@
56
56
  display: none;
57
57
  }
58
58
  }
59
+
60
+ // Active/pressed state for toolbar toggle buttons (bold, italic, underline, lists,
61
+ // link, blockquote, …). The buttons apply `.active` via
62
+ // `[class.active]="editor.isActive(...)"`; this gives the pressed treatment so the
63
+ // currently-applied formatting is visible. Without it the class is set but nothing
64
+ // renders — the toggle never "lights up". Uses the consumer's `--primary` tokens
65
+ // for contrast against the toolbar surface (`--muted`); the hover selector keeps an
66
+ // active button highlighted while the pointer is over it.
67
+ document-engine-toolbar .btn.active {
68
+ &,
69
+ &:hover:not(:disabled) {
70
+ background-color: var(--primary, hsl(222.2 47.4% 11.2%));
71
+ color: var(--primary-foreground, hsl(210 40% 98%));
72
+ }
73
+ }