@nascentdigital/funnel-core 4.4.10 → 4.4.14

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.
@@ -105,4 +105,10 @@ export declare namespace ChangeSet {
105
105
  };
106
106
  }
107
107
  type ConstructorArgs = Omit<Data, 'errors'>;
108
+ type AddChangeArgs<TChange extends Change> = {
109
+ changes: TChange[];
110
+ change: Omit<TChange, 'attribution'>;
111
+ userId: string;
112
+ };
113
+ function addChange<TChange extends Change>({ changes, change, userId }: AddChangeArgs<TChange>): void;
108
114
  }
@@ -81,7 +81,7 @@ class ChangeSet {
81
81
  }
82
82
  addPageChange(userId, change) {
83
83
  // apply change
84
- addChange({
84
+ ChangeSet.addChange({
85
85
  changes: this._changes,
86
86
  change,
87
87
  userId
@@ -98,7 +98,7 @@ class ChangeSet {
98
98
  }
99
99
  addCollectionChange(userId, change) {
100
100
  // apply change
101
- addChange({
101
+ ChangeSet.addChange({
102
102
  changes: this._changes,
103
103
  change,
104
104
  userId
@@ -115,7 +115,7 @@ class ChangeSet {
115
115
  }
116
116
  addAssetChange(userId, change) {
117
117
  // apply change
118
- addChange({
118
+ ChangeSet.addChange({
119
119
  changes: this._changes,
120
120
  change,
121
121
  userId
@@ -162,26 +162,27 @@ exports.ChangeSet = ChangeSet;
162
162
  New.filter = filter;
163
163
  })(New = Data.New || (Data.New = {}));
164
164
  })(Data = ChangeSet.Data || (ChangeSet.Data = {}));
165
- })(ChangeSet || (exports.ChangeSet = ChangeSet = {}));
166
- function addChange({ changes, change, userId }) {
167
- // update existing page
168
- const updatedOn = Date.now();
169
- const changeIndex = changes.findIndex(({ id }) => change.id === id);
170
- if (changeIndex >= 0) {
171
- // update attribution (skip if it's the same change by the same user)
172
- const _a = changes[changeIndex], { type, attribution } = _a, existingChange = __rest(_a, ["type", "attribution"]);
173
- const lastAttribution = attribution.length > 0
174
- ? attribution[attribution.length - 1]
175
- : undefined;
176
- if ((lastAttribution === null || lastAttribution === void 0 ? void 0 : lastAttribution.userId) !== userId || change.type !== type) {
177
- attribution.push({ userId, updatedOn });
165
+ function addChange({ changes, change, userId }) {
166
+ // update existing page
167
+ const updatedOn = Date.now();
168
+ const changeIndex = changes.findIndex(({ id }) => change.id === id);
169
+ if (changeIndex >= 0) {
170
+ // update attribution (skip if it's the same change by the same user)
171
+ const _a = changes[changeIndex], { type, attribution } = _a, existingChange = __rest(_a, ["type", "attribution"]);
172
+ const lastAttribution = attribution.length > 0
173
+ ? attribution[attribution.length - 1]
174
+ : undefined;
175
+ if ((lastAttribution === null || lastAttribution === void 0 ? void 0 : lastAttribution.userId) !== userId || change.type !== type) {
176
+ attribution.push({ userId, updatedOn });
177
+ }
178
+ // update change
179
+ changes[changeIndex] = Object.assign(Object.assign(Object.assign({}, existingChange), change), { type,
180
+ attribution });
181
+ }
182
+ // or add a new one
183
+ else {
184
+ changes.push(Object.assign(Object.assign({}, change), { attribution: [{ userId, updatedOn }] }));
178
185
  }
179
- // update change
180
- changes[changeIndex] = Object.assign(Object.assign(Object.assign({}, existingChange), change), { type,
181
- attribution });
182
- }
183
- // or add a new one
184
- else {
185
- changes.push(Object.assign(Object.assign({}, change), { attribution: [{ userId, updatedOn }] }));
186
186
  }
187
- }
187
+ ChangeSet.addChange = addChange;
188
+ })(ChangeSet || (exports.ChangeSet = ChangeSet = {}));
@@ -1,9 +1,24 @@
1
1
  export type BlockLayout = {
2
2
  name: string;
3
3
  root: BlockLayout.Root;
4
- fullbleed: boolean;
4
+ tight?: BlockLayout.Tight;
5
+ tightMobile?: BlockLayout.Tight;
6
+ tightDesktop?: BlockLayout.Tight;
5
7
  };
6
8
  export declare namespace BlockLayout {
9
+ type Tight = 'above' | 'below' | 'both' | 'none';
10
+ namespace Tight {
11
+ const values: ReadonlyArray<Tight>;
12
+ function parse(value: string, fallback?: Tight): Tight;
13
+ }
14
+ type Ratio = '1/1' | '1/2' | '1/3' | '2/1' | '3/1' | '2/3' | '3/2';
15
+ namespace Ratio {
16
+ const values: ReadonlyArray<Ratio>;
17
+ function parse(value: string, fallback?: Ratio): Ratio;
18
+ }
19
+ namespace FullBleed {
20
+ function parse(value: string, fallback?: boolean): boolean;
21
+ }
7
22
  type Root = Row | Column | Stack;
8
23
  type Container = Row | Row.Nested | Column | Column.Nested | Stack | Stack.Nested;
9
24
  type Row = {
@@ -11,10 +26,14 @@ export declare namespace BlockLayout {
11
26
  items: ReadonlyArray<Column.Nested | Stack.Nested | Panel>;
12
27
  alignX?: Alignment;
13
28
  alignY?: Alignment;
29
+ ratio?: Ratio;
30
+ ratioMobile?: Ratio;
31
+ ratioDesktop?: Ratio;
14
32
  };
15
33
  namespace Row {
16
34
  type Nested = Omit<Row, 'items'> & {
17
35
  items: ReadonlyArray<Panel>;
36
+ mobileOrder?: MobileOrder;
18
37
  };
19
38
  }
20
39
  type Column = {
@@ -26,6 +45,7 @@ export declare namespace BlockLayout {
26
45
  namespace Column {
27
46
  type Nested = Omit<Column, 'items'> & {
28
47
  items: ReadonlyArray<Panel>;
48
+ mobileOrder?: MobileOrder;
29
49
  };
30
50
  }
31
51
  type Stack = {
@@ -37,6 +57,7 @@ export declare namespace BlockLayout {
37
57
  namespace Stack {
38
58
  type Nested = Omit<Stack, 'items'> & {
39
59
  items: ReadonlyArray<Panel>;
60
+ mobileOrder?: MobileOrder;
40
61
  };
41
62
  }
42
63
  type NestedItem = Row.Nested | Column.Nested | Stack.Nested | Panel;
@@ -46,18 +67,25 @@ export declare namespace BlockLayout {
46
67
  type Base = {
47
68
  key: Key;
48
69
  mobileOrder?: MobileOrder;
49
- alignX: Alignment;
50
- alignY: Alignment;
51
- size: Size;
70
+ alignX?: Alignment;
71
+ alignY?: Alignment;
72
+ size?: Size;
73
+ fullBleed?: boolean;
74
+ fullBleedMobile?: boolean;
75
+ fullBleedDesktop?: boolean;
52
76
  };
53
77
  }
54
78
  type Message = Panel.Base & {
55
79
  type: 'message';
56
- textAlign: TextAlignment;
80
+ textAlign?: TextAlignment;
81
+ textAlignMobile?: TextAlignment;
82
+ textAlignDesktop?: TextAlignment;
57
83
  };
58
84
  type Visual = Panel.Base & {
59
85
  type: 'visual';
60
86
  aspectRatio: AspectRatio;
87
+ aspectRatioMobile?: AspectRatio;
88
+ aspectRatioDesktop?: AspectRatio;
61
89
  };
62
90
  type List = Panel.Base & {
63
91
  type: 'list';
@@ -17,6 +17,71 @@ const errors_1 = require("../../errors");
17
17
  // extension
18
18
  var BlockLayout;
19
19
  (function (BlockLayout) {
20
+ let Tight;
21
+ (function (Tight) {
22
+ // values
23
+ Tight.values = ['above', 'below', 'both', 'none'];
24
+ // helper
25
+ function parse(value, fallback) {
26
+ // return if it's a match
27
+ if (Tight.values.includes(value)) {
28
+ return value;
29
+ }
30
+ // or fallback if we have one
31
+ else if (fallback) {
32
+ return fallback;
33
+ }
34
+ // or throw
35
+ else {
36
+ throw new errors_1.ArgumentError(`Invalid BlockLayout.Tight "${value}"`, 'value');
37
+ }
38
+ }
39
+ Tight.parse = parse;
40
+ })(Tight = BlockLayout.Tight || (BlockLayout.Tight = {}));
41
+ let Ratio;
42
+ (function (Ratio) {
43
+ // values
44
+ Ratio.values = ['1/1', '1/2', '1/3', '2/1', '3/1', '2/3', '3/2'];
45
+ // helper
46
+ function parse(value, fallback) {
47
+ // return if it's a match
48
+ if (Ratio.values.includes(value)) {
49
+ return value;
50
+ }
51
+ // or fallback if we have one
52
+ else if (fallback) {
53
+ return fallback;
54
+ }
55
+ // or throw
56
+ else {
57
+ throw new errors_1.ArgumentError(`Invalid BlockLayout.Ratio "${value}"`, 'value');
58
+ }
59
+ }
60
+ Ratio.parse = parse;
61
+ })(Ratio = BlockLayout.Ratio || (BlockLayout.Ratio = {}));
62
+ let FullBleed;
63
+ (function (FullBleed) {
64
+ // helper
65
+ function parse(value, fallback) {
66
+ // return true if the string is 'true'
67
+ if (value === 'true') {
68
+ return true;
69
+ }
70
+ // return false if the string is 'false'
71
+ else if (value === 'false') {
72
+ return false;
73
+ }
74
+ // or fallback if we have one
75
+ else if (fallback !== undefined) {
76
+ return fallback;
77
+ }
78
+ // or throw
79
+ else {
80
+ throw new errors_1.ArgumentError(`Invalid BlockLayout.FullBleed "${value}"`, 'value');
81
+ }
82
+ }
83
+ FullBleed.parse = parse;
84
+ })(FullBleed = BlockLayout.FullBleed || (BlockLayout.FullBleed = {}));
20
85
  let List;
21
86
  (function (List) {
22
87
  let ItemType;
@@ -153,28 +218,32 @@ var BlockLayout;
153
218
  // parse xml (not sure if this throws on error)
154
219
  const blockJson = xmlParser.parse(xml);
155
220
  // walk json to create block data
156
- return parseeBlock(blockJson[0]);
221
+ return parseBlock(blockJson[0]);
157
222
  }
158
223
  BlockLayout.parse = parse;
159
224
  })(BlockLayout || (exports.BlockLayout = BlockLayout = {}));
160
225
  // helpers
161
- function parseeBlock(json) {
226
+ function parseBlock(json) {
162
227
  // parse block
163
228
  const block = ParsedElement.parse({ data: json, expectedTags: ['block'], expectedChildren: 1 });
229
+ const tight = BlockLayout.Tight.parse(block.attributes['tight'], 'none');
164
230
  // return block
165
231
  return {
166
232
  name: block.attributes['name'],
233
+ tight,
234
+ tightMobile: BlockLayout.Tight.parse(block.attributes['tightMobile'], tight),
235
+ tightDesktop: BlockLayout.Tight.parse(block.attributes['tightDesktop'], tight),
167
236
  root: parseRoot(ParsedElement.parse({ data: block.children[0], expectedTags: ['row', 'col', 'stack'] })),
168
- fullbleed: block.attributes['fullbleed'] === 'true'
169
237
  };
170
238
  }
171
239
  function parseRoot({ tag, attributes, children }) {
240
+ const ratio = BlockLayout.Ratio.parse(attributes['ratio'], '1/1');
172
241
  // parse attributes
173
- const itemAttributes = parseItemAttributes(attributes);
242
+ const _a = parseItemAttributes(attributes), { size } = _a, itemAttributes = __rest(_a, ["size"]);
174
243
  // create root
175
244
  switch (tag) {
176
245
  case 'row':
177
- return Object.assign(Object.assign({ type: 'row' }, itemAttributes), { items: children.map(data => parseNestedItem(ParsedElement.parse({
246
+ return Object.assign(Object.assign({ type: 'row' }, itemAttributes), { ratio, ratioMobile: BlockLayout.Ratio.parse(attributes['ratioMobile'], ratio), ratioDesktop: BlockLayout.Ratio.parse(attributes['ratioDesktop'], ratio), items: children.map(data => parseNestedItem(ParsedElement.parse({
178
247
  data,
179
248
  expectedTags: ParsedElement.tags.filter(t => t !== tag)
180
249
  }))) });
@@ -195,9 +264,14 @@ function parseRoot({ tag, attributes, children }) {
195
264
  function parseNestedItem({ tag, attributes, children }) {
196
265
  // parse attributes
197
266
  const _a = parseItemAttributes(attributes), { size } = _a, itemAttributes = __rest(_a, ["size"]);
267
+ const ratio = BlockLayout.Ratio.parse(attributes['ratio'], '1/1');
198
268
  // create container
199
269
  switch (tag) {
200
270
  case 'row':
271
+ return Object.assign(Object.assign({ type: 'row' }, itemAttributes), { ratio, ratioMobile: BlockLayout.Ratio.parse(attributes['ratioMobile'], ratio), ratioDesktop: BlockLayout.Ratio.parse(attributes['ratioDesktop'], ratio), items: children.map(data => parsePanel(Object.assign({}, ParsedElement.parse({
272
+ data,
273
+ expectedTags: ParsedElement.tags.filter(t => t !== 'row' && t !== 'col')
274
+ })))) });
201
275
  case 'col':
202
276
  case 'stack':
203
277
  return Object.assign(Object.assign({ type: tag === 'col' ? 'column' : tag }, itemAttributes), { items: children.map(data => parsePanel(Object.assign({}, ParsedElement.parse({
@@ -212,13 +286,24 @@ function parsePanel({ tag, attributes }) {
212
286
  var _a;
213
287
  const key = (_a = attributes['key']) !== null && _a !== void 0 ? _a : tag;
214
288
  const itemAttributes = parseItemAttributes(attributes);
289
+ const textAlign = BlockLayout.TextAlignment.parse(attributes['textAlign'], 'left');
290
+ const aspectRatio = BlockLayout.AspectRatio.parse(attributes['aspect'], 'auto');
291
+ const fullBleed = BlockLayout.FullBleed.parse(attributes['fullBleed'], false);
292
+ const fullBleedMobile = BlockLayout.FullBleed.parse(attributes['fullBleedMobile'], fullBleed);
293
+ const fullBleedDesktop = BlockLayout.FullBleed.parse(attributes['fullBleedDesktop'], fullBleed);
215
294
  switch (tag) {
216
295
  case 'message':
217
- return Object.assign(Object.assign({ type: 'message', key }, itemAttributes), { textAlign: BlockLayout.TextAlignment.parse(attributes['textAlign'], 'left') });
296
+ return Object.assign(Object.assign({ type: 'message', key }, itemAttributes), { textAlign, textAlignMobile: BlockLayout.TextAlignment.parse(attributes['textAlignMobile'], textAlign), textAlignDesktop: BlockLayout.TextAlignment.parse(attributes['textAlignDesktop'], textAlign), fullBleed,
297
+ fullBleedMobile,
298
+ fullBleedDesktop });
218
299
  case 'visual':
219
- return Object.assign(Object.assign({ type: 'visual', key }, itemAttributes), { aspectRatio: BlockLayout.AspectRatio.parse(attributes['aspect'], 'auto') });
300
+ return Object.assign(Object.assign({ type: 'visual', key }, itemAttributes), { aspectRatio, aspectRatioMobile: BlockLayout.AspectRatio.parse(attributes['aspectMobile'], aspectRatio), aspectRatioDesktop: BlockLayout.AspectRatio.parse(attributes['aspectDesktop'], aspectRatio), fullBleed,
301
+ fullBleedMobile,
302
+ fullBleedDesktop });
220
303
  case 'list':
221
- return Object.assign(Object.assign({ type: 'list', key }, itemAttributes), { itemType: BlockLayout.List.ItemType.parse(attributes['itemType'], 'message+visual') });
304
+ return Object.assign(Object.assign({ type: 'list', key }, itemAttributes), { fullBleed,
305
+ fullBleedMobile,
306
+ fullBleedDesktop, itemType: BlockLayout.List.ItemType.parse(attributes['itemType'], 'message+visual') });
222
307
  default:
223
308
  throw new errors_1.ArgumentError(`Invalid XML BlockLayout - encountered "${tag}" where instead of BlockLayout.Panel`);
224
309
  }
@@ -226,9 +311,9 @@ function parsePanel({ tag, attributes }) {
226
311
  function parseItemAttributes(attributes) {
227
312
  return {
228
313
  mobileOrder: BlockLayout.MobileOrder.parse(attributes['mobileOrder']),
229
- size: BlockLayout.Size.parse(attributes['size'], 'full'),
230
314
  alignX: BlockLayout.Alignment.parse(attributes['alignX'], 'start'),
231
- alignY: BlockLayout.Alignment.parse(attributes['alignY'], 'start')
315
+ alignY: BlockLayout.Alignment.parse(attributes['alignY'], 'start'),
316
+ size: BlockLayout.Size.parse(attributes['size'], 'full'),
232
317
  };
233
318
  }
234
319
  var ParsedElement;
@@ -11,6 +11,8 @@ export declare class Field<TSchema extends Schema.Field.Type = Schema.Field.Type
11
11
  private readonly publishedValue;
12
12
  private _initialValue;
13
13
  private _value;
14
+ private _modifiedOn?;
15
+ private _savingOn?;
14
16
  private _editing;
15
17
  private _sandboxed;
16
18
  private _sandboxedValue;
@@ -48,6 +50,8 @@ export declare class Field<TSchema extends Schema.Field.Type = Schema.Field.Type
48
50
  didFocus(): void;
49
51
  validate(silent?: boolean): ReadonlyArray<FieldValidator.Error>;
50
52
  migrate(previous: Field<TSchema>): void;
53
+ onSaveStart(): void;
54
+ onSaveComplete(): void;
51
55
  reset(initialValue?: Schema.Field.Data<TSchema> | null): void;
52
56
  clone({ schema, eventBus, name, required }?: Field.CloneArgs<TSchema>): Field<TSchema>;
53
57
  private raiseEvent;
@@ -122,6 +122,7 @@ class Field {
122
122
  }
123
123
  // update value
124
124
  this._value = value;
125
+ this._modifiedOn = Date.now();
125
126
  // raise change event
126
127
  this.raiseEvent('changed');
127
128
  // raise error (if any)
@@ -134,6 +135,7 @@ class Field {
134
135
  }
135
136
  // update value
136
137
  this._value = value;
138
+ this._modifiedOn = Date.now();
137
139
  // raise error (if any)
138
140
  this.validate(true);
139
141
  }
@@ -189,6 +191,7 @@ class Field {
189
191
  }
190
192
  // rollback sandbox
191
193
  this._value = this._sandboxedValue;
194
+ this._modifiedOn = Date.now();
192
195
  this._sandboxed = false;
193
196
  // raise event
194
197
  this.raiseEvent('changed');
@@ -244,10 +247,25 @@ class Field {
244
247
  }
245
248
  migrate(previous) {
246
249
  this._value = previous.value;
250
+ this._modifiedOn = previous._modifiedOn;
247
251
  this._editing = true;
248
252
  this._sandboxed = previous._sandboxed;
249
253
  this._sandboxedValue = previous._sandboxedValue;
250
254
  }
255
+ onSaveStart() {
256
+ this._savingOn = Date.now();
257
+ }
258
+ onSaveComplete() {
259
+ // reset to initial value if not modified or if save is newer
260
+ const { _modifiedOn, _savingOn } = this;
261
+ if (_savingOn && (!_modifiedOn || _savingOn >= _modifiedOn)) {
262
+ this._initialValue = this._value;
263
+ delete this._optIn;
264
+ delete this._optOut;
265
+ }
266
+ // clear saving flag
267
+ delete this._savingOn;
268
+ }
251
269
  reset(initialValue) {
252
270
  // reset initial value if it's set
253
271
  if (initialValue !== undefined) {
@@ -8,11 +8,13 @@ export declare class FieldList {
8
8
  readonly schema: FieldList.ListSchema;
9
9
  readonly composition: FieldList.ListComposition;
10
10
  private _items;
11
+ private _modifiedOn?;
12
+ private _savingOn?;
11
13
  readonly id: string;
12
14
  readonly name: string;
13
15
  readonly required: boolean;
14
16
  private readonly eventBus;
15
- readonly initialValue: FieldList.ListValue | undefined;
17
+ private _initialValue;
16
18
  readonly publishedValue: FieldList.ListValue | undefined;
17
19
  private readonly _validator;
18
20
  private _errors;
@@ -20,6 +22,7 @@ export declare class FieldList {
20
22
  private _optOut?;
21
23
  constructor(args: FieldList.ConstructorArgs);
22
24
  get events$(): Observable<FieldList.Event>;
25
+ get initialValue(): Schema.Field.Data.ForCompositeType<FieldList.ListSchema> | undefined;
23
26
  get items(): ReadonlyArray<FieldList.Item>;
24
27
  get isNew(): boolean;
25
28
  get exists(): boolean;
@@ -45,6 +48,8 @@ export declare class FieldList {
45
48
  removeItem(itemId: string): void;
46
49
  removeField(field: Field | ObjectField): void;
47
50
  moveItem(fromIndex: number, toIndex: number): void;
51
+ onSaveStart(): void;
52
+ onSaveComplete(): void;
48
53
  reset(initialValue?: FieldList.ListValue | null): void;
49
54
  clone(): FieldList;
50
55
  private raiseEvent;
@@ -70,7 +70,7 @@ class FieldList {
70
70
  this.id = args.id;
71
71
  this.name = args.name;
72
72
  this.required = args.required;
73
- this.initialValue = args.initialValue;
73
+ this._initialValue = args.initialValue;
74
74
  this.publishedValue = args.publishedValue;
75
75
  this._validator = new FieldListValidator_1.FieldListValidator(this.schema);
76
76
  // validate
@@ -79,6 +79,9 @@ class FieldList {
79
79
  get events$() {
80
80
  return this.eventBus.events$.pipe((0, rxjs_1.filter)(FieldList.Event.filter), (0, rxjs_1.filter)(e => e.field === this));
81
81
  }
82
+ get initialValue() {
83
+ return this._initialValue;
84
+ }
82
85
  get items() {
83
86
  return this._items;
84
87
  }
@@ -127,6 +130,7 @@ class FieldList {
127
130
  value,
128
131
  publishedValue
129
132
  });
133
+ this._modifiedOn = Date.now();
130
134
  // revalidate
131
135
  this.validate();
132
136
  // raise event
@@ -221,6 +225,7 @@ class FieldList {
221
225
  };
222
226
  // add to list
223
227
  this._items = [...this._items, item];
228
+ this._modifiedOn = Date.now();
224
229
  // revalidate
225
230
  this.validate();
226
231
  // raise event
@@ -234,6 +239,8 @@ class FieldList {
234
239
  this._items = this._items.filter(({ id }) => !itemIds.includes(id));
235
240
  // raise event (if there are changes)
236
241
  if (this._items.length !== itemCount) {
242
+ // update modified
243
+ this._modifiedOn = Date.now();
237
244
  // revalidate
238
245
  this.validate();
239
246
  // raise change
@@ -268,11 +275,26 @@ class FieldList {
268
275
  items.splice(toIndex, 0, item);
269
276
  // update items
270
277
  this._items = items;
278
+ this._modifiedOn = Date.now();
271
279
  // revalidate
272
280
  this.validate();
273
281
  // raise change
274
282
  this.raiseEvent('changed');
275
283
  }
284
+ onSaveStart() {
285
+ this._savingOn = Date.now();
286
+ }
287
+ onSaveComplete() {
288
+ // reset to initial value if not modified or if save is newer
289
+ const { _modifiedOn, _savingOn } = this;
290
+ if (_savingOn && (!_modifiedOn || _savingOn >= _modifiedOn)) {
291
+ this._initialValue = this.value;
292
+ delete this._optIn;
293
+ delete this._optOut;
294
+ }
295
+ // clear saving flag
296
+ delete this._savingOn;
297
+ }
276
298
  reset(initialValue) {
277
299
  // re-bind new initial values
278
300
  if (initialValue || initialValue === null) {
@@ -287,6 +309,8 @@ class FieldList {
287
309
  else {
288
310
  this._items.forEach(({ field }) => field.reset());
289
311
  }
312
+ // mark modified
313
+ this._modifiedOn = Date.now();
290
314
  // revalidate
291
315
  this.validate();
292
316
  // raise final change
@@ -15,6 +15,8 @@ export declare class ObjectField {
15
15
  private readonly _content;
16
16
  private readonly _fields;
17
17
  private _initialValue;
18
+ private _modifiedOn?;
19
+ private _savingOn?;
18
20
  private _optIn?;
19
21
  private _optOut?;
20
22
  constructor(args: ObjectField.ConstructorArgs);
@@ -40,6 +42,8 @@ export declare class ObjectField {
40
42
  get shouldFocus(): boolean;
41
43
  didFocus(): boolean;
42
44
  getString(key: string | undefined): string | undefined;
45
+ onSaveStart(): void;
46
+ onSaveComplete(): void;
43
47
  reset(initialValue?: ObjectField.Value | null): void;
44
48
  clone(): ObjectField;
45
49
  private raiseEvent;
@@ -93,6 +93,8 @@ class ObjectField {
93
93
  field.value = values[key];
94
94
  }
95
95
  });
96
+ // mark modified
97
+ this._modifiedOn = Date.now();
96
98
  }
97
99
  get modified() {
98
100
  // modified if opted in/our
@@ -179,9 +181,25 @@ class ObjectField {
179
181
  return undefined;
180
182
  }
181
183
  }
184
+ onSaveStart() {
185
+ this._savingOn = Date.now();
186
+ }
187
+ onSaveComplete() {
188
+ // reset to initial value if not modified or if save is newer
189
+ const { _modifiedOn, _savingOn } = this;
190
+ if (_savingOn && (!_modifiedOn || _savingOn >= _modifiedOn)) {
191
+ this._initialValue = this.value;
192
+ delete this._optIn;
193
+ delete this._optOut;
194
+ }
195
+ // clear saving flag
196
+ delete this._savingOn;
197
+ }
182
198
  reset(initialValue) {
183
199
  var _a;
200
+ // reset fields using specified value
184
201
  if (initialValue !== undefined) {
202
+ // update initial value
185
203
  this._initialValue = initialValue !== null && initialValue !== void 0 ? initialValue : undefined;
186
204
  // re-bind new initial values
187
205
  const values = (_a = initialValue === null || initialValue === void 0 ? void 0 : initialValue.value) !== null && _a !== void 0 ? _a : {};
@@ -198,10 +216,12 @@ class ObjectField {
198
216
  }
199
217
  });
200
218
  }
201
- // reset each field if there's no initial value
219
+ // or reset each field if there's no initial value
202
220
  else {
203
221
  this._fields.forEach(field => field.reset());
204
222
  }
223
+ // track modified
224
+ this._modifiedOn = Date.now();
205
225
  // raise final change
206
226
  this.raiseEvent('changed');
207
227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nascentdigital/funnel-core",
3
- "version": "4.4.10",
3
+ "version": "4.4.14",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/nascentdigital/funnel.git",
6
6
  "directory": "libs/core"
@@ -63,5 +63,5 @@
63
63
  "jest-tsd": "^0.2.2",
64
64
  "ts-jest": "^29.3.2"
65
65
  },
66
- "gitHead": "3aa36fa46c32ad625f569ea4b9e43aab80937ff4"
66
+ "gitHead": "242a9e0979c03d4037eb2462c5b01b2a095e8e8f"
67
67
  }