@solidev/data 1.1.0 → 1.1.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.
- package/AGENTS.md +21 -2
- package/fesm2022/solidev-data-mdedit.mjs +27 -112
- package/fesm2022/solidev-data-mdedit.mjs.map +1 -1
- package/fesm2022/solidev-data-richedit.mjs +31 -139
- package/fesm2022/solidev-data-richedit.mjs.map +1 -1
- package/fesm2022/solidev-data.mjs +288 -11
- package/fesm2022/solidev-data.mjs.map +1 -1
- package/package.json +1 -1
- package/types/solidev-data-mdedit.d.ts +13 -64
- package/types/solidev-data-richedit.d.ts +8 -65
- package/types/solidev-data.d.ts +180 -8
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, viewChild, inject, DestroyRef, afterNextRender, Component, signal, computed
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
2
|
+
import { input, viewChild, inject, DestroyRef, afterNextRender, Component, signal, computed } from '@angular/core';
|
|
3
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
4
|
+
import * as i1 from '@angular/forms';
|
|
5
|
+
import { NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
|
+
import { FieldEditorBase, SafeHtmlPipe } from '@solidev/data';
|
|
7
8
|
import { chainCommands, exitCode, toggleMark, baseKeymap, setBlockType, lift, wrapIn } from 'prosemirror-commands';
|
|
8
9
|
import { redo, undo, history } from 'prosemirror-history';
|
|
9
10
|
import { inputRules, wrappingInputRule, textblockTypeInputRule } from 'prosemirror-inputrules';
|
|
@@ -12,8 +13,6 @@ import { bulletList, orderedList, listItem, liftListItem, sinkListItem, splitLis
|
|
|
12
13
|
import { PluginKey, Plugin, EditorState } from 'prosemirror-state';
|
|
13
14
|
import { DecorationSet, Decoration, EditorView } from 'prosemirror-view';
|
|
14
15
|
import { Schema, DOMParser, DOMSerializer } from 'prosemirror-model';
|
|
15
|
-
import { uniqueId, asText, fieldValues } from '@solidev/data';
|
|
16
|
-
import { firstValueFrom } from 'rxjs';
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* The document schema `richedit` stores and edits.
|
|
@@ -1114,7 +1113,8 @@ const RichEditToolbars = {
|
|
|
1114
1113
|
* Shipped as a separate entry point (`@solidev/data/richedit`) so that the
|
|
1115
1114
|
* `prosemirror-*` dependencies stay optional for consumers who do not need
|
|
1116
1115
|
* them. It follows the same `dd` / `inline` / `form` layout convention as
|
|
1117
|
-
* `<data-dispedit>`,
|
|
1116
|
+
* `<data-dispedit>`, but is used directly rather than through it: `dispedit`
|
|
1117
|
+
* lives in the primary entry point and cannot import a secondary one.
|
|
1118
1118
|
*
|
|
1119
1119
|
* The engine used to be `ngx-editor`, which stopped at Angular 19. The
|
|
1120
1120
|
* replacement lives in `./prose` and speaks the same schema, so stored values
|
|
@@ -1131,156 +1131,48 @@ const RichEditToolbars = {
|
|
|
1131
1131
|
* <data-richedit [model]="thing" field="description">Description</data-richedit>
|
|
1132
1132
|
* ```
|
|
1133
1133
|
*/
|
|
1134
|
-
class RicheditComponent {
|
|
1135
|
-
/** Model instance holding the field. */
|
|
1136
|
-
/** Id tying the label to the editor. */
|
|
1137
|
-
inputId = uniqueId('data-richedit');
|
|
1138
|
-
model;
|
|
1139
|
-
/** Name of the rich text field to edit. */
|
|
1140
|
-
field;
|
|
1141
|
-
/** Whether {@link toggleEdit} is allowed to enable editing. */
|
|
1142
|
-
editable = true;
|
|
1143
|
-
/** Whether the editor is currently enabled. Defaults to true. */
|
|
1144
|
-
edit = true;
|
|
1145
|
-
/**
|
|
1146
|
-
* Layout, and whether {@link save} persists.
|
|
1147
|
-
*
|
|
1148
|
-
* `dd` renders a `<dt>`/`<dd>` block and is the only mode that saves to the
|
|
1149
|
-
* API; `inline` and `form` render a label plus the editor and leave saving to
|
|
1150
|
-
* the caller.
|
|
1151
|
-
*/
|
|
1152
|
-
mode = 'dd';
|
|
1153
|
-
/** Hide label (for inline forms) */
|
|
1154
|
-
hideLabel = false;
|
|
1155
|
-
/**
|
|
1156
|
-
* Hide the built-in save button, for callers driving persistence themselves
|
|
1157
|
-
* from the {@link changed} output.
|
|
1158
|
-
*/
|
|
1159
|
-
hideButton = false;
|
|
1160
|
-
/**
|
|
1161
|
-
* Form control backing the editor.
|
|
1162
|
-
*
|
|
1163
|
-
* When supplied, the component uses it as-is and skips its own setup — no
|
|
1164
|
-
* field manager lookup, no seeding from the model, and no {@link changed}
|
|
1165
|
-
* emissions. When absent, one is created and wired up from `[model]` and
|
|
1166
|
-
* `[field]`.
|
|
1167
|
-
*/
|
|
1168
|
-
fc;
|
|
1134
|
+
class RicheditComponent extends FieldEditorBase {
|
|
1169
1135
|
/** Toolbar preset name from {@link RichEditToolbars}, or an explicit toolbar. */
|
|
1170
|
-
toolbar = 'default'
|
|
1171
|
-
|
|
1172
|
-
changed = new EventEmitter();
|
|
1173
|
-
/** Field manager for {@link field}; only resolved when `fc` is created here. */
|
|
1174
|
-
manager;
|
|
1175
|
-
/** Whether the manager declares the field required. */
|
|
1176
|
-
required;
|
|
1136
|
+
toolbar = input('default', /* @ts-ignore */
|
|
1137
|
+
...(ngDevMode ? [{ debugName: "toolbar" }] : /* istanbul ignore next */ []));
|
|
1177
1138
|
/** Underlying editor instance; destroyed with the component. */
|
|
1178
1139
|
editor;
|
|
1179
1140
|
/** Unused; kept for backwards compatibility. */
|
|
1180
1141
|
html = '';
|
|
1181
1142
|
/** Toolbar actually rendered, resolved from {@link toolbar}. */
|
|
1182
|
-
realToolbar;
|
|
1143
|
+
realToolbar = [];
|
|
1144
|
+
destroyRef = inject(DestroyRef);
|
|
1183
1145
|
/**
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
* relaying changes to {@link changed}.
|
|
1146
|
+
* Build the engine and resolve the toolbar, then let the base resolve the
|
|
1147
|
+
* control and relay its changes.
|
|
1187
1148
|
*/
|
|
1188
1149
|
ngOnInit() {
|
|
1189
1150
|
this.editor = new Editor();
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
// No toolbar at all: leave `realToolbar` undefined.
|
|
1151
|
+
this.destroyRef.onDestroy(() => this.editor.destroy());
|
|
1152
|
+
const toolbar = this.toolbar();
|
|
1153
|
+
if (toolbar === 'none') {
|
|
1154
|
+
this.realToolbar = [];
|
|
1195
1155
|
}
|
|
1196
|
-
else {
|
|
1197
|
-
this.realToolbar =
|
|
1198
|
-
}
|
|
1199
|
-
if (!this.fc) {
|
|
1200
|
-
this.fc = new FormControl('');
|
|
1201
|
-
if (this.model && this.field) {
|
|
1202
|
-
this.manager = this.model.FM(this.field);
|
|
1203
|
-
this.required = this.manager?.required || false;
|
|
1204
|
-
this.fc.setValue(asText(fieldValues(this.model)[this.field]), {
|
|
1205
|
-
emitEvent: false,
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1208
|
-
if (this.edit) {
|
|
1209
|
-
this.fc.enable();
|
|
1210
|
-
}
|
|
1211
|
-
else {
|
|
1212
|
-
this.fc.disable();
|
|
1213
|
-
}
|
|
1214
|
-
this.fc.valueChanges.subscribe((v) => this.changed.emit(v));
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
/** Destroy the editor instance to release its resources. */
|
|
1218
|
-
ngOnDestroy() {
|
|
1219
|
-
this.editor.destroy();
|
|
1220
|
-
}
|
|
1221
|
-
/**
|
|
1222
|
-
* Switch between read-only and editing by enabling or disabling the control.
|
|
1223
|
-
* Forces read-only when `[editable]` is false.
|
|
1224
|
-
*/
|
|
1225
|
-
toggleEdit() {
|
|
1226
|
-
if (this.editable) {
|
|
1227
|
-
if (this.edit) {
|
|
1228
|
-
this.fc.disable();
|
|
1229
|
-
this.edit = false;
|
|
1230
|
-
}
|
|
1231
|
-
else {
|
|
1232
|
-
this.fc.enable();
|
|
1233
|
-
this.edit = true;
|
|
1234
|
-
}
|
|
1156
|
+
else if (typeof toolbar === 'string') {
|
|
1157
|
+
this.realToolbar = RichEditToolbars[toolbar] ?? [];
|
|
1235
1158
|
}
|
|
1236
1159
|
else {
|
|
1237
|
-
this.
|
|
1238
|
-
this.edit = false;
|
|
1160
|
+
this.realToolbar = toolbar;
|
|
1239
1161
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
* In `inline` / `form` modes the model is updated in memory but no request is
|
|
1246
|
-
* sent, leaving the save to the surrounding form. Does nothing without both a
|
|
1247
|
-
* `[model]` and a `[field]`.
|
|
1248
|
-
*/
|
|
1249
|
-
async save() {
|
|
1250
|
-
if (this.model && this.field) {
|
|
1251
|
-
this.model.setFV(this.field, this.fc.value);
|
|
1252
|
-
if (this.mode === 'dd') {
|
|
1253
|
-
await firstValueFrom(this.model.update([this.field], { updateModel: true }));
|
|
1254
|
-
}
|
|
1162
|
+
super.ngOnInit();
|
|
1163
|
+
if (!this.fc()) {
|
|
1164
|
+
this.control.valueChanges
|
|
1165
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1166
|
+
.subscribe((value) => this.changed.emit(value));
|
|
1255
1167
|
}
|
|
1256
1168
|
}
|
|
1257
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: RicheditComponent, deps:
|
|
1258
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: RicheditComponent, isStandalone: true, selector: "data-richedit", inputs: {
|
|
1169
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: RicheditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1170
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: RicheditComponent, isStandalone: true, selector: "data-richedit", inputs: { toolbar: { classPropertyName: "toolbar", publicName: "toolbar", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "<ng-template #editorTemplate>\n @if (!showEditor()) {\n <!--\n Read-only: the stored HTML, injected as trusted markup. See\n SafeHtmlPipe for why it is not sanitized.\n -->\n <div\n class=\"editable data-richedit__view\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"toggleEdit()\"\n (keydown.enter)=\"toggleEdit()\"\n (keydown.space)=\"$event.preventDefault(); toggleEdit()\"\n [innerHTML]=\"displayValue() | safeHtml\"\n ></div>\n } @else {\n <div class=\"data-richedit__wrapper\">\n @if (realToolbar.length) {\n <data-richedit-menubar [editor]=\"editor\" [toolbar]=\"realToolbar\"></data-richedit-menubar>\n }\n <data-richedit-editor [editor]=\"editor\" [inputId]=\"inputId\" [formControl]=\"control\"></data-richedit-editor>\n </div>\n @if (!hideButton()) {\n <button class=\"btn btn-primary btn-sm w-100 mt-1\" (click)=\"save()\">\n <i class=\"bi bi-save me-2\"></i>\n Enregistrer\n </button>\n }\n }\n</ng-template>\n<ng-template #titleTpl>\n <ng-content></ng-content>\n</ng-template>\n<!-- Dd display-->\n@if (mode() === \"dd\") {\n @if (!hideLabel()) {\n <dt [class.required]=\"required()\">\n <span\n class=\"editable\"\n [attr.id]=\"labelId\"\n (click)=\"toggleEdit()\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"toggleEdit()\"\n (keydown.space)=\"$event.preventDefault(); toggleEdit()\"\n >\n <ng-container [ngTemplateOutlet]=\"titleTpl\"></ng-container>\n </span>\n </dt>\n }\n <dd [class.mb-0]=\"hideLabel()\">\n <ng-container [ngTemplateOutlet]=\"editorTemplate\"></ng-container>\n </dd>\n}\n<!-- Inline display-->\n@if (mode() === \"inline\") {\n @if (!hideLabel()) {\n <label [class.required]=\"required()\" [attr.id]=\"labelId\" [attr.for]=\"inputId\">\n <ng-container [ngTemplateOutlet]=\"titleTpl\"></ng-container>\n </label>\n }\n <ng-container [ngTemplateOutlet]=\"editorTemplate\"></ng-container>\n}\n<!-- Form display-->\n@if (mode() === \"form\") {\n @if (!hideLabel()) {\n <label [class.required]=\"required()\" [attr.id]=\"labelId\" [attr.for]=\"inputId\">\n <ng-container [ngTemplateOutlet]=\"titleTpl\"></ng-container>\n </label>\n }\n <ng-container [ngTemplateOutlet]=\"editorTemplate\"></ng-container>\n}\n", styles: [".data-richedit__wrapper{border:var(--bs-border-width, 1px) solid var(--bs-border-color, #dee2e6);border-radius:var(--bs-border-radius, .375rem);background:var(--bs-body-bg, #fff);overflow:hidden}.data-richedit__wrapper:focus-within{border-color:var(--bs-primary-border-subtle, #86b7fe);box-shadow:0 0 0 .25rem rgba(var(--bs-primary-rgb, 13, 110, 253),.25)}.data-richedit__view>*:first-child{margin-top:0}.data-richedit__view>*:last-child{margin-bottom:0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: RicheditEditorComponent, selector: "data-richedit-editor", inputs: ["editor", "inputId"] }, { kind: "component", type: RicheditMenubarComponent, selector: "data-richedit-menubar", inputs: ["editor", "toolbar"] }, { kind: "pipe", type: SafeHtmlPipe, name: "safeHtml" }] });
|
|
1259
1171
|
}
|
|
1260
1172
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: RicheditComponent, decorators: [{
|
|
1261
1173
|
type: Component,
|
|
1262
|
-
args: [{ selector: 'data-richedit', imports: [
|
|
1263
|
-
}], propDecorators: {
|
|
1264
|
-
type: Input
|
|
1265
|
-
}], field: [{
|
|
1266
|
-
type: Input
|
|
1267
|
-
}], editable: [{
|
|
1268
|
-
type: Input
|
|
1269
|
-
}], edit: [{
|
|
1270
|
-
type: Input
|
|
1271
|
-
}], mode: [{
|
|
1272
|
-
type: Input
|
|
1273
|
-
}], hideLabel: [{
|
|
1274
|
-
type: Input
|
|
1275
|
-
}], hideButton: [{
|
|
1276
|
-
type: Input
|
|
1277
|
-
}], fc: [{
|
|
1278
|
-
type: Input
|
|
1279
|
-
}], toolbar: [{
|
|
1280
|
-
type: Input
|
|
1281
|
-
}], changed: [{
|
|
1282
|
-
type: Output
|
|
1283
|
-
}] } });
|
|
1174
|
+
args: [{ selector: 'data-richedit', imports: [NgTemplateOutlet, ReactiveFormsModule, RicheditEditorComponent, RicheditMenubarComponent, SafeHtmlPipe], template: "<ng-template #editorTemplate>\n @if (!showEditor()) {\n <!--\n Read-only: the stored HTML, injected as trusted markup. See\n SafeHtmlPipe for why it is not sanitized.\n -->\n <div\n class=\"editable data-richedit__view\"\n role=\"button\"\n tabindex=\"0\"\n (click)=\"toggleEdit()\"\n (keydown.enter)=\"toggleEdit()\"\n (keydown.space)=\"$event.preventDefault(); toggleEdit()\"\n [innerHTML]=\"displayValue() | safeHtml\"\n ></div>\n } @else {\n <div class=\"data-richedit__wrapper\">\n @if (realToolbar.length) {\n <data-richedit-menubar [editor]=\"editor\" [toolbar]=\"realToolbar\"></data-richedit-menubar>\n }\n <data-richedit-editor [editor]=\"editor\" [inputId]=\"inputId\" [formControl]=\"control\"></data-richedit-editor>\n </div>\n @if (!hideButton()) {\n <button class=\"btn btn-primary btn-sm w-100 mt-1\" (click)=\"save()\">\n <i class=\"bi bi-save me-2\"></i>\n Enregistrer\n </button>\n }\n }\n</ng-template>\n<ng-template #titleTpl>\n <ng-content></ng-content>\n</ng-template>\n<!-- Dd display-->\n@if (mode() === \"dd\") {\n @if (!hideLabel()) {\n <dt [class.required]=\"required()\">\n <span\n class=\"editable\"\n [attr.id]=\"labelId\"\n (click)=\"toggleEdit()\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"toggleEdit()\"\n (keydown.space)=\"$event.preventDefault(); toggleEdit()\"\n >\n <ng-container [ngTemplateOutlet]=\"titleTpl\"></ng-container>\n </span>\n </dt>\n }\n <dd [class.mb-0]=\"hideLabel()\">\n <ng-container [ngTemplateOutlet]=\"editorTemplate\"></ng-container>\n </dd>\n}\n<!-- Inline display-->\n@if (mode() === \"inline\") {\n @if (!hideLabel()) {\n <label [class.required]=\"required()\" [attr.id]=\"labelId\" [attr.for]=\"inputId\">\n <ng-container [ngTemplateOutlet]=\"titleTpl\"></ng-container>\n </label>\n }\n <ng-container [ngTemplateOutlet]=\"editorTemplate\"></ng-container>\n}\n<!-- Form display-->\n@if (mode() === \"form\") {\n @if (!hideLabel()) {\n <label [class.required]=\"required()\" [attr.id]=\"labelId\" [attr.for]=\"inputId\">\n <ng-container [ngTemplateOutlet]=\"titleTpl\"></ng-container>\n </label>\n }\n <ng-container [ngTemplateOutlet]=\"editorTemplate\"></ng-container>\n}\n", styles: [".data-richedit__wrapper{border:var(--bs-border-width, 1px) solid var(--bs-border-color, #dee2e6);border-radius:var(--bs-border-radius, .375rem);background:var(--bs-body-bg, #fff);overflow:hidden}.data-richedit__wrapper:focus-within{border-color:var(--bs-primary-border-subtle, #86b7fe);box-shadow:0 0 0 .25rem rgba(var(--bs-primary-rgb, 13, 110, 253),.25)}.data-richedit__view>*:first-child{margin-top:0}.data-richedit__view>*:last-child{margin-bottom:0}\n"] }]
|
|
1175
|
+
}], propDecorators: { toolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbar", required: false }] }] } });
|
|
1284
1176
|
|
|
1285
1177
|
/**
|
|
1286
1178
|
* Toolbar configuration for `<data-richedit>`.
|