@codeauradev/ngx-wind-editor 1.0.1 → 1.0.2

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.
@@ -1,6 +1,9 @@
1
- :host {
1
+ wind-editor,
2
+ .wind-editor {
2
3
  display: block;
3
4
  min-width: 0;
5
+ color: #1c1917;
6
+ font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif);
4
7
  }
5
8
 
6
9
  .wind-frame {
@@ -182,3 +185,328 @@
182
185
  .dark .wind-doc code {
183
186
  background: color-mix(in oklab, white 8%, transparent);
184
187
  }
188
+
189
+ .wind-editor.is-disabled {
190
+ opacity: 0.7;
191
+ }
192
+
193
+ .wind-shell {
194
+ border: 1px solid #e7e5e4;
195
+ border-radius: 0.75rem;
196
+ background: #fff;
197
+ color: #1c1917;
198
+ box-shadow: 0 1px 2px rgb(28 25 23 / 0.06);
199
+ }
200
+
201
+ .wind-editor.is-invalid .wind-shell {
202
+ border-color: #b91c1c;
203
+ }
204
+
205
+ .wind-toolbar,
206
+ .wind-pop,
207
+ .wind-meta,
208
+ .wind-error,
209
+ .wind-source {
210
+ border-color: #e7e5e4;
211
+ }
212
+
213
+ .wind-toolbar {
214
+ display: flex;
215
+ gap: 0.125rem;
216
+ overflow-x: auto;
217
+ border-bottom: 1px solid #e7e5e4;
218
+ padding: 0.375rem;
219
+ }
220
+
221
+ .wind-tool,
222
+ .wind-primary,
223
+ .wind-quiet,
224
+ .wind-field,
225
+ .wind-source-input {
226
+ font: inherit;
227
+ }
228
+
229
+ .wind-tool {
230
+ display: inline-flex;
231
+ height: 2.25rem;
232
+ flex: 0 0 auto;
233
+ align-items: center;
234
+ justify-content: center;
235
+ gap: 0.25rem;
236
+ border: 0;
237
+ border-radius: 0.375rem;
238
+ background: transparent;
239
+ padding: 0 0.5rem;
240
+ color: #57534e;
241
+ font-size: 0.875rem;
242
+ line-height: 1;
243
+ cursor: pointer;
244
+ }
245
+
246
+ .wind-tool:hover {
247
+ background: #f5f5f4;
248
+ }
249
+
250
+ .wind-tool:disabled {
251
+ opacity: 0.4;
252
+ cursor: default;
253
+ }
254
+
255
+ .wind-tool[aria-pressed='true'] {
256
+ background: #0f766e;
257
+ color: #fff;
258
+ }
259
+
260
+ .wind-tool-label {
261
+ padding: 0 0.125rem;
262
+ font-size: 0.75rem;
263
+ font-weight: 600;
264
+ letter-spacing: 0.04em;
265
+ }
266
+
267
+ .wind-pop,
268
+ .wind-source {
269
+ display: flex;
270
+ flex-wrap: wrap;
271
+ align-items: center;
272
+ gap: 0.5rem;
273
+ border-bottom: 1px solid #e7e5e4;
274
+ padding: 0.5rem 0.75rem;
275
+ }
276
+
277
+ .wind-source {
278
+ display: block;
279
+ border-bottom: 0;
280
+ border-top: 1px solid #e7e5e4;
281
+ }
282
+
283
+ .wind-field,
284
+ .wind-source-input {
285
+ height: 2.25rem;
286
+ min-width: 0;
287
+ flex: 1 1 12rem;
288
+ border: 1px solid #d6d3d1;
289
+ border-radius: 0.375rem;
290
+ background: #fff;
291
+ padding: 0 0.5rem;
292
+ color: inherit;
293
+ font-size: 0.875rem;
294
+ outline: none;
295
+ }
296
+
297
+ .wind-field-short {
298
+ flex: 0 1 9rem;
299
+ width: 9rem;
300
+ }
301
+
302
+ .wind-field:focus,
303
+ .wind-source-input:focus {
304
+ border-color: #0f766e;
305
+ }
306
+
307
+ .wind-source-input {
308
+ display: block;
309
+ box-sizing: border-box;
310
+ width: 100%;
311
+ height: 10rem;
312
+ resize: vertical;
313
+ padding: 0.5rem;
314
+ background: #fafaf9;
315
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
316
+ font-size: 0.75rem;
317
+ line-height: 1.5;
318
+ }
319
+
320
+ .wind-primary,
321
+ .wind-quiet {
322
+ height: 2.25rem;
323
+ border: 0;
324
+ border-radius: 0.375rem;
325
+ cursor: pointer;
326
+ font-size: 0.875rem;
327
+ }
328
+
329
+ .wind-primary {
330
+ background: #0f766e;
331
+ padding: 0 0.75rem;
332
+ color: #fff;
333
+ font-weight: 500;
334
+ }
335
+
336
+ .wind-quiet {
337
+ background: transparent;
338
+ padding: 0 0.5rem;
339
+ color: #57534e;
340
+ }
341
+
342
+ .wind-source .wind-primary {
343
+ margin-top: 0.5rem;
344
+ background: #1c1917;
345
+ }
346
+
347
+ .wind-surface {
348
+ position: relative;
349
+ min-height: 12rem;
350
+ }
351
+
352
+ .wind-menu {
353
+ position: fixed;
354
+ z-index: 30;
355
+ max-height: 16rem;
356
+ width: 14rem;
357
+ overflow: auto;
358
+ margin: 0;
359
+ border: 1px solid #e7e5e4;
360
+ border-radius: 0.5rem;
361
+ background: #fff;
362
+ padding: 0.25rem 0;
363
+ list-style: none;
364
+ box-shadow: 0 10px 24px rgb(28 25 23 / 0.12);
365
+ }
366
+
367
+ .wind-option {
368
+ display: flex;
369
+ width: 100%;
370
+ align-items: baseline;
371
+ justify-content: space-between;
372
+ gap: 0.75rem;
373
+ border: 0;
374
+ background: transparent;
375
+ padding: 0.375rem 0.75rem;
376
+ color: inherit;
377
+ font: inherit;
378
+ font-size: 0.875rem;
379
+ text-align: left;
380
+ cursor: pointer;
381
+ }
382
+
383
+ .wind-option:hover,
384
+ .wind-option.is-active {
385
+ background: #f5f5f4;
386
+ }
387
+
388
+ .wind-option.is-active {
389
+ background: #0f766e;
390
+ color: #fff;
391
+ }
392
+
393
+ .wind-option-hint {
394
+ font-size: 0.75rem;
395
+ opacity: 0.7;
396
+ }
397
+
398
+ .wind-meta,
399
+ .wind-error {
400
+ display: flex;
401
+ align-items: center;
402
+ justify-content: space-between;
403
+ gap: 0.75rem;
404
+ border-top: 1px solid #e7e5e4;
405
+ padding: 0.375rem 0.75rem;
406
+ color: #78716c;
407
+ font-size: 0.75rem;
408
+ }
409
+
410
+ .wind-meta-hint {
411
+ color: #a8a29e;
412
+ }
413
+
414
+ .wind-error {
415
+ color: #b91c1c;
416
+ }
417
+
418
+ .wind-sr {
419
+ position: absolute;
420
+ width: 1px;
421
+ height: 1px;
422
+ padding: 0;
423
+ margin: -1px;
424
+ overflow: hidden;
425
+ clip: rect(0, 0, 0, 0);
426
+ white-space: nowrap;
427
+ border: 0;
428
+ }
429
+
430
+ .dark .wind-shell,
431
+ .dark.wind-editor .wind-shell {
432
+ border-color: #292524;
433
+ background: #0c0a09;
434
+ color: #f5f5f4;
435
+ }
436
+
437
+ .dark .wind-editor.is-invalid .wind-shell,
438
+ .dark.wind-editor.is-invalid .wind-shell {
439
+ border-color: #f87171;
440
+ }
441
+
442
+ .dark .wind-toolbar,
443
+ .dark .wind-pop,
444
+ .dark .wind-meta,
445
+ .dark .wind-source,
446
+ .dark.wind-editor .wind-toolbar,
447
+ .dark.wind-editor .wind-pop,
448
+ .dark.wind-editor .wind-meta,
449
+ .dark.wind-editor .wind-source {
450
+ border-color: #292524;
451
+ }
452
+
453
+ .dark .wind-tool,
454
+ .dark .wind-quiet,
455
+ .dark.wind-editor .wind-tool,
456
+ .dark.wind-editor .wind-quiet {
457
+ color: #d6d3d1;
458
+ }
459
+
460
+ .dark .wind-tool:hover,
461
+ .dark.wind-editor .wind-tool:hover {
462
+ background: #1c1917;
463
+ }
464
+
465
+ .dark .wind-tool[aria-pressed='true'],
466
+ .dark.wind-editor .wind-tool[aria-pressed='true'] {
467
+ background: #0d9488;
468
+ color: #fff;
469
+ }
470
+
471
+ .dark .wind-field,
472
+ .dark .wind-source-input,
473
+ .dark.wind-editor .wind-field,
474
+ .dark.wind-editor .wind-source-input {
475
+ border-color: #44403c;
476
+ background: #1c1917;
477
+ color: #f5f5f4;
478
+ }
479
+
480
+ .dark .wind-source .wind-primary,
481
+ .dark.wind-editor .wind-source .wind-primary {
482
+ background: #f5f5f4;
483
+ color: #1c1917;
484
+ }
485
+
486
+ .dark .wind-menu,
487
+ .dark.wind-editor .wind-menu {
488
+ border-color: #44403c;
489
+ background: #1c1917;
490
+ }
491
+
492
+ .dark .wind-option:hover,
493
+ .dark.wind-editor .wind-option:hover {
494
+ background: #292524;
495
+ }
496
+
497
+ .dark .wind-option.is-active,
498
+ .dark.wind-editor .wind-option.is-active {
499
+ background: #0f766e;
500
+ color: #fff;
501
+ }
502
+
503
+ .dark .wind-meta,
504
+ .dark.wind-editor .wind-meta {
505
+ color: #a8a29e;
506
+ }
507
+
508
+ .dark .wind-error,
509
+ .dark.wind-editor .wind-error {
510
+ border-color: #7f1d1d;
511
+ color: #fca5a5;
512
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeauradev/ngx-wind-editor",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "WYSIWYG HTML editor for Angular 22, styled with Tailwind CSS v4.",
5
5
  "keywords": [
6
6
  "angular",
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@angular/core": "^22.0.0",
37
+ "@angular/forms": "^22.0.0",
37
38
  "tailwindcss": "^4.0.0"
38
39
  },
39
40
  "peerDependenciesMeta": {
@@ -1,4 +1,5 @@
1
1
  import * as Y from "yjs";
2
+ import { FormValueControl, ValidationError } from "@angular/forms/signals";
2
3
  import * as i0 from "@angular/core";
3
4
  declare const BLOCK_TYPES: readonly ["paragraph", "heading", "blockquote", "code", "bullet", "ordered", "rule", "image"];
4
5
  type BlockType = (typeof BLOCK_TYPES)[number];
@@ -179,7 +180,7 @@ type WindCommand = {
179
180
  };
180
181
  interface WindMount {
181
182
  destroy(): void;
182
- focus(): void;
183
+ focus(options?: FocusOptions): void;
183
184
  exec(command: WindCommand): void;
184
185
  setReadonly(readonly: boolean): void;
185
186
  setPlaceholder(value: string): void;
@@ -202,10 +203,19 @@ interface Tool {
202
203
  label: string;
203
204
  icon: string;
204
205
  }
205
- export declare class WindEditor {
206
+ export declare class WindEditor implements FormValueControl<string> {
207
+ /** Signal forms bind this model. `[formField]` requires the public name `value`. */
208
+ readonly value: import("@angular/core").ModelSignal<string>;
209
+ /** Same document as `value`, so existing `[(html)]` bindings keep working. */
206
210
  readonly html: import("@angular/core").ModelSignal<string>;
207
211
  readonly placeholder: import("@angular/core").InputSignal<string>;
208
- readonly readOnly: import("@angular/core").InputSignal<boolean>;
212
+ readonly readonly: import("@angular/core").InputSignal<boolean>;
213
+ readonly disabled: import("@angular/core").InputSignal<boolean>;
214
+ readonly invalid: import("@angular/core").InputSignal<boolean>;
215
+ readonly required: import("@angular/core").InputSignal<boolean>;
216
+ readonly touched: import("@angular/core").InputSignal<boolean>;
217
+ readonly errors: import("@angular/core").InputSignal<readonly ValidationError.WithOptionalFieldTree[]>;
218
+ readonly touch: import("@angular/core").OutputEmitterRef<void>;
209
219
  readonly showToolbar: import("@angular/core").InputSignal<boolean>;
210
220
  /** Preset name, or the buttons to show, in order. */
211
221
  readonly toolbar: import("@angular/core").InputSignal<readonly ("paragraph" | "code" | "bullet" | "ordered" | "rule" | "image" | "bold" | "italic" | "underline" | "strike" | "link" | "source" | "h1" | "h2" | "quote" | "undo" | "redo" | "codeblock" | "clear")[] | WindToolbarPreset>;
@@ -219,6 +229,7 @@ export declare class WindEditor {
219
229
  protected readonly sourceOpen: import("@angular/core").WritableSignal<boolean>;
220
230
  protected readonly sourceDraft: import("@angular/core").WritableSignal<string>;
221
231
  protected readonly slashIndex: import("@angular/core").WritableSignal<number>;
232
+ protected readonly errorText: import("@angular/core").Signal<string>;
222
233
  protected readonly slashItems: import("@angular/core").Signal<SlashItem[]>;
223
234
  private readonly surfaceRef;
224
235
  private readonly destroyRef;
@@ -226,7 +237,7 @@ export declare class WindEditor {
226
237
  private echo;
227
238
  constructor();
228
239
  protected pressed(id: string): boolean;
229
- protected disabled(id: string): boolean;
240
+ protected toolDisabled(id: string): boolean;
230
241
  protected run(id: string): void;
231
242
  protected pickSlash(item: SlashItem): void;
232
243
  protected applyLink(event: Event): void;
@@ -235,9 +246,17 @@ export declare class WindEditor {
235
246
  protected applySource(): void;
236
247
  protected onDraft(event: Event, field: 'link' | 'src' | 'alt' | 'source'): void;
237
248
  private attach;
249
+ /** Signal forms calls this when the bound field should take focus. */
250
+ focus(options?: FocusOptions): void;
251
+ protected onFocusOut(event: FocusEvent): void;
238
252
  private onKey;
239
253
  static ɵfac: i0.ɵɵFactoryDeclaration<WindEditor, never>;
240
254
  static ɵcmp: i0.ɵɵComponentDeclaration<WindEditor, "wind-editor", never, {
255
+ "value": {
256
+ "alias": "value";
257
+ "required": false;
258
+ "isSignal": true;
259
+ };
241
260
  "html": {
242
261
  "alias": "html";
243
262
  "required": false;
@@ -248,11 +267,36 @@ export declare class WindEditor {
248
267
  "required": false;
249
268
  "isSignal": true;
250
269
  };
251
- "readOnly": {
270
+ "readonly": {
252
271
  "alias": "readonly";
253
272
  "required": false;
254
273
  "isSignal": true;
255
274
  };
275
+ "disabled": {
276
+ "alias": "disabled";
277
+ "required": false;
278
+ "isSignal": true;
279
+ };
280
+ "invalid": {
281
+ "alias": "invalid";
282
+ "required": false;
283
+ "isSignal": true;
284
+ };
285
+ "required": {
286
+ "alias": "required";
287
+ "required": false;
288
+ "isSignal": true;
289
+ };
290
+ "touched": {
291
+ "alias": "touched";
292
+ "required": false;
293
+ "isSignal": true;
294
+ };
295
+ "errors": {
296
+ "alias": "errors";
297
+ "required": false;
298
+ "isSignal": true;
299
+ };
256
300
  "showToolbar": {
257
301
  "alias": "showToolbar";
258
302
  "required": false;
@@ -264,7 +308,9 @@ export declare class WindEditor {
264
308
  "isSignal": true;
265
309
  };
266
310
  }, {
311
+ "value": "valueChange";
267
312
  "html": "htmlChange";
313
+ "touch": "touch";
268
314
  }, never, never, true, never>;
269
315
  }
270
316
  export type { BlockData, BlockType, EditorSnapshot, MarkName, Selection, SlashChoice, SlashItem, TextAttrs, WindTool, WindToolbarPreset };