@dream-encode/wp-js-plugin-utils 0.6.2 → 0.8.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.
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.SEVERITY_WARN = exports.SEVERITY_BLOCK = exports.EVENT_STOCK_CHANGED = exports.EVENT_SAVE_REQUESTED = exports.EVENT_SAVE_COMMITTED = exports.EVENT_SAVE_BLOCKED = exports.EVENT_RELISTING_DETECTED = exports.EVENT_RELISTING_CONFIRMED = exports.EVENT_RELISTING_CANCELLED = exports.EVENT_PRODUCT_READY = exports.EVENT_DUPLICATE_AWAITING_FIRST_SAVE = exports.EDIT_PRODUCT_STORE_NAME = exports.CONTEXT_RELISTING = exports.CONTEXT_DUPLICATE = exports.CONTEXT_ALWAYS = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Lifecycle event names and context values emitted by the edit product kernel.
|
|
9
|
+
*
|
|
10
|
+
* These mirror the constants in `max-marine-edit-product-shared-state`. Consumers import
|
|
11
|
+
* them from here so no plugin hand-copies an event string - which is exactly how
|
|
12
|
+
* `MMEPSSStockQuantityChanged` ended up declared by hand in two plugins at once.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const EDIT_PRODUCT_STORE_NAME = exports.EDIT_PRODUCT_STORE_NAME = 'max-marine-edit-product-shared-state';
|
|
16
|
+
const EVENT_PRODUCT_READY = exports.EVENT_PRODUCT_READY = 'max-marine/edit-product/product:ready';
|
|
17
|
+
const EVENT_STOCK_CHANGED = exports.EVENT_STOCK_CHANGED = 'max-marine/edit-product/stock:changed';
|
|
18
|
+
const EVENT_RELISTING_DETECTED = exports.EVENT_RELISTING_DETECTED = 'max-marine/edit-product/relisting:detected';
|
|
19
|
+
const EVENT_RELISTING_CONFIRMED = exports.EVENT_RELISTING_CONFIRMED = 'max-marine/edit-product/relisting:confirmed';
|
|
20
|
+
const EVENT_RELISTING_CANCELLED = exports.EVENT_RELISTING_CANCELLED = 'max-marine/edit-product/relisting:cancelled';
|
|
21
|
+
const EVENT_DUPLICATE_AWAITING_FIRST_SAVE = exports.EVENT_DUPLICATE_AWAITING_FIRST_SAVE = 'max-marine/edit-product/duplicate:awaiting-first-save';
|
|
22
|
+
const EVENT_SAVE_REQUESTED = exports.EVENT_SAVE_REQUESTED = 'max-marine/edit-product/save:requested';
|
|
23
|
+
const EVENT_SAVE_BLOCKED = exports.EVENT_SAVE_BLOCKED = 'max-marine/edit-product/save:blocked';
|
|
24
|
+
const EVENT_SAVE_COMMITTED = exports.EVENT_SAVE_COMMITTED = 'max-marine/edit-product/save:committed';
|
|
25
|
+
const CONTEXT_ALWAYS = exports.CONTEXT_ALWAYS = 'always';
|
|
26
|
+
const CONTEXT_DUPLICATE = exports.CONTEXT_DUPLICATE = 'duplicate';
|
|
27
|
+
const CONTEXT_RELISTING = exports.CONTEXT_RELISTING = 'relisting';
|
|
28
|
+
const SEVERITY_BLOCK = exports.SEVERITY_BLOCK = 'block';
|
|
29
|
+
const SEVERITY_WARN = exports.SEVERITY_WARN = 'warn';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
getRegistry: true,
|
|
8
|
+
onEditProductEvent: true,
|
|
9
|
+
registerAction: true,
|
|
10
|
+
registerField: true,
|
|
11
|
+
registerRequirement: true,
|
|
12
|
+
registerOnReady: true
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "getRegistry", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () {
|
|
17
|
+
return _registry.getRegistry;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(exports, "onEditProductEvent", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () {
|
|
23
|
+
return _registry.onEditProductEvent;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(exports, "registerAction", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
get: function () {
|
|
29
|
+
return _registry.registerAction;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(exports, "registerField", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
get: function () {
|
|
35
|
+
return _registry.registerField;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(exports, "registerOnReady", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function () {
|
|
41
|
+
return _registerOnReady.default;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(exports, "registerRequirement", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get: function () {
|
|
47
|
+
return _registry.registerRequirement;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
var _registry = require("./registry");
|
|
51
|
+
var _registerOnReady = _interopRequireDefault(require("./registerOnReady"));
|
|
52
|
+
var _constants = require("./constants");
|
|
53
|
+
Object.keys(_constants).forEach(function (key) {
|
|
54
|
+
if (key === "default" || key === "__esModule") return;
|
|
55
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
56
|
+
if (key in exports && exports[key] === _constants[key]) return;
|
|
57
|
+
Object.defineProperty(exports, key, {
|
|
58
|
+
enumerable: true,
|
|
59
|
+
get: function () {
|
|
60
|
+
return _constants[key];
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _domReady = _interopRequireDefault(require("@wordpress/dom-ready"));
|
|
8
|
+
var _registry = require("./registry");
|
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
/**
|
|
11
|
+
* Register a batch of edit product extensions on `domReady`.
|
|
12
|
+
*
|
|
13
|
+
* Fields are registered before actions and requirements, because both refer to fields by
|
|
14
|
+
* key and the registry checks that the key resolves.
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} registrations Registration map.
|
|
17
|
+
* @param {Object[]} [registrations.fields] Field definitions.
|
|
18
|
+
* @param {Object[]} [registrations.actions] Action definitions.
|
|
19
|
+
* @param {Object[]} [registrations.requirements] Requirement definitions.
|
|
20
|
+
* @return {void}
|
|
21
|
+
*/
|
|
22
|
+
const registerOnReady = (registrations = {}) => {
|
|
23
|
+
const {
|
|
24
|
+
fields = [],
|
|
25
|
+
actions = [],
|
|
26
|
+
requirements = []
|
|
27
|
+
} = registrations;
|
|
28
|
+
(0, _domReady.default)(() => {
|
|
29
|
+
fields.forEach(field => {
|
|
30
|
+
(0, _registry.registerField)(field);
|
|
31
|
+
});
|
|
32
|
+
actions.forEach(action => {
|
|
33
|
+
(0, _registry.registerAction)(action);
|
|
34
|
+
});
|
|
35
|
+
requirements.forEach(requirement => {
|
|
36
|
+
(0, _registry.registerRequirement)(requirement);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
var _default = exports.default = registerOnReady;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.registerRequirement = exports.registerField = exports.registerAction = exports.onEditProductEvent = exports.getRegistry = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Lightweight wrappers around the global edit product registry exposed by
|
|
9
|
+
* `max-marine-edit-product-shared-state`.
|
|
10
|
+
*
|
|
11
|
+
* EPSS owns the registry itself - the field, action and requirement buckets, the
|
|
12
|
+
* lifecycle events and the save gate. These helpers give consuming plugins a way to
|
|
13
|
+
* register into it without hand-copying the namespace, in the same shape as the
|
|
14
|
+
* Data Migrations wrappers alongside them.
|
|
15
|
+
*
|
|
16
|
+
* Unlike the Data Migrations registry, this one is created by whichever script reaches
|
|
17
|
+
* it first. A consumer whose script happens to run before EPSS's still registers
|
|
18
|
+
* successfully, and EPSS picks up what is already there.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const NAMESPACE = 'maxMarineEditProduct';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the shared registry, creating the slot if nothing has yet.
|
|
25
|
+
*
|
|
26
|
+
* Returns null outside a browser, and null if the slot exists but does not look like a
|
|
27
|
+
* registry - which happens only if something else has claimed the global.
|
|
28
|
+
*
|
|
29
|
+
* @return {Object|null}
|
|
30
|
+
*/
|
|
31
|
+
const getRegistry = () => {
|
|
32
|
+
if ('undefined' === typeof window) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const registry = window[NAMESPACE];
|
|
36
|
+
if (!registry || 'function' !== typeof registry.registerField) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return registry;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Register a field this plugin owns.
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} field Field definition.
|
|
46
|
+
* @param {string} field.key Stable key other plugins refer to this field by.
|
|
47
|
+
* @param {string} field.label Human label, shown to the employee.
|
|
48
|
+
* @param {string} field.owner Owning plugin slug.
|
|
49
|
+
* @param {string} [field.group] Group name used to bucket it in modals.
|
|
50
|
+
* @param {Function} field.read Returns the current value.
|
|
51
|
+
* @param {Function} field.clear Empties the field.
|
|
52
|
+
* @param {Function} [field.isEmpty] Whether a value counts as empty.
|
|
53
|
+
* @param {Function} [field.focus] Moves focus to the field.
|
|
54
|
+
* @param {Function} [field.isDisabled] Whether the field should be skipped entirely.
|
|
55
|
+
* @return {boolean}
|
|
56
|
+
*/
|
|
57
|
+
exports.getRegistry = getRegistry;
|
|
58
|
+
const registerField = field => {
|
|
59
|
+
const registry = getRegistry();
|
|
60
|
+
if (!registry) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return registry.registerField(field);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register an action bound to one or more lifecycle contexts.
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} action Action definition.
|
|
70
|
+
* @param {string} action.id Unique action id.
|
|
71
|
+
* @param {string|string[]} action.on Contexts, e.g. 'relisting' or 'duplicate'.
|
|
72
|
+
* @param {string} [action.label] Human label, shown to the employee.
|
|
73
|
+
* @param {string|string[]} [action.clears] Field keys to clear.
|
|
74
|
+
* @param {Function} [action.run] Extra work beyond clearing fields.
|
|
75
|
+
* @return {boolean}
|
|
76
|
+
*/
|
|
77
|
+
exports.registerField = registerField;
|
|
78
|
+
const registerAction = action => {
|
|
79
|
+
const registry = getRegistry();
|
|
80
|
+
if (!registry) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return registry.registerAction(action);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Register a requirement that gates saving.
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} requirement Requirement definition.
|
|
90
|
+
* @param {string} requirement.id Unique requirement id.
|
|
91
|
+
* @param {string} [requirement.field] Field key this requirement is about.
|
|
92
|
+
* @param {*} [requirement.when] Context name, array, or predicate.
|
|
93
|
+
* @param {string} [requirement.severity] 'block' or 'warn'. Default 'block'.
|
|
94
|
+
* @param {string} [requirement.message] Message shown to the employee.
|
|
95
|
+
* @param {Function} [requirement.validate] Custom check, in place of the field's own.
|
|
96
|
+
* @return {boolean}
|
|
97
|
+
*/
|
|
98
|
+
exports.registerAction = registerAction;
|
|
99
|
+
const registerRequirement = requirement => {
|
|
100
|
+
const registry = getRegistry();
|
|
101
|
+
if (!registry) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return registry.registerRequirement(requirement);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Subscribe to a lifecycle event emitted by the kernel.
|
|
109
|
+
*
|
|
110
|
+
* Returns an unsubscribe function, so a caller never has to hold on to the emitter or
|
|
111
|
+
* remember the matching `off()` call.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} event Event name.
|
|
114
|
+
* @param {Function} callback Handler.
|
|
115
|
+
* @return {Function} Call to unsubscribe.
|
|
116
|
+
*/
|
|
117
|
+
exports.registerRequirement = registerRequirement;
|
|
118
|
+
const onEditProductEvent = (event, callback) => {
|
|
119
|
+
const store = window?.wp?.data?.select?.('max-marine-edit-product-shared-state');
|
|
120
|
+
const emitter = store && 'function' === typeof store.getEventEmitter ? store.getEventEmitter() : null;
|
|
121
|
+
if (!emitter) {
|
|
122
|
+
return () => {};
|
|
123
|
+
}
|
|
124
|
+
emitter.on(event, callback);
|
|
125
|
+
return () => {
|
|
126
|
+
emitter.off(event, callback);
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
exports.onEditProductEvent = onEditProductEvent;
|
|
@@ -5,6 +5,16 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _element = require("@wordpress/element");
|
|
8
|
+
/**
|
|
9
|
+
* Read the raw section key carried in the URL hash.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.6.3
|
|
12
|
+
* @return {string} Hash without its leading `#`, or an empty string.
|
|
13
|
+
*/
|
|
14
|
+
const readHash = () => {
|
|
15
|
+
return window.location.hash.replace(/^#/, '');
|
|
16
|
+
};
|
|
17
|
+
|
|
8
18
|
/**
|
|
9
19
|
* Read the section key carried in the URL hash.
|
|
10
20
|
*
|
|
@@ -13,7 +23,7 @@ var _element = require("@wordpress/element");
|
|
|
13
23
|
* @return {string|null} Matching section key, or null when the hash names nothing known.
|
|
14
24
|
*/
|
|
15
25
|
const readSectionFromHash = keys => {
|
|
16
|
-
const hash =
|
|
26
|
+
const hash = readHash();
|
|
17
27
|
return keys.includes(hash) ? hash : null;
|
|
18
28
|
};
|
|
19
29
|
|
|
@@ -26,6 +36,15 @@ const readSectionFromHash = keys => {
|
|
|
26
36
|
* the browser Back button still leaves the settings page instead of walking back
|
|
27
37
|
* through the sections that were opened.
|
|
28
38
|
*
|
|
39
|
+
* A page whose sections depend on a setting registers them in two passes: the first
|
|
40
|
+
* render happens before `/wp/v2/settings` answers, so any section gated behind a
|
|
41
|
+
* value is missing from `sections` at that point. A hash naming one of those is
|
|
42
|
+
* therefore unknown when the page mounts. Rather than discard it and fall back to
|
|
43
|
+
* the first section, the requested key is parked and adopted as soon as it appears,
|
|
44
|
+
* which is what makes a link to a gated section work. The parked key is dropped the
|
|
45
|
+
* moment the reader chooses a section themselves, so a late arriving section can
|
|
46
|
+
* never pull them away from what they are looking at.
|
|
47
|
+
*
|
|
29
48
|
* @since 0.6.0
|
|
30
49
|
* @param {Array} sections Section definitions.
|
|
31
50
|
* @return {Array} The active section key and a setter for it.
|
|
@@ -35,15 +54,26 @@ const useActiveSection = sections => {
|
|
|
35
54
|
const keys = (0, _element.useMemo)(() => {
|
|
36
55
|
return keySignature.split('|').filter(Boolean);
|
|
37
56
|
}, [keySignature]);
|
|
57
|
+
const requestedSection = (0, _element.useRef)(null);
|
|
38
58
|
const [activeKey, setActiveKey] = (0, _element.useState)(() => {
|
|
59
|
+
const hash = readHash();
|
|
60
|
+
if (hash && !keys.includes(hash)) {
|
|
61
|
+
requestedSection.current = hash;
|
|
62
|
+
}
|
|
39
63
|
return readSectionFromHash(keys) || keys[0] || '';
|
|
40
64
|
});
|
|
41
65
|
(0, _element.useEffect)(() => {
|
|
42
66
|
const handleHashChange = () => {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
|
|
67
|
+
const hash = readHash();
|
|
68
|
+
if (!hash) {
|
|
69
|
+
return;
|
|
46
70
|
}
|
|
71
|
+
if (keys.includes(hash)) {
|
|
72
|
+
requestedSection.current = null;
|
|
73
|
+
setActiveKey(hash);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
requestedSection.current = hash;
|
|
47
77
|
};
|
|
48
78
|
window.addEventListener('hashchange', handleHashChange);
|
|
49
79
|
return () => {
|
|
@@ -51,11 +81,25 @@ const useActiveSection = sections => {
|
|
|
51
81
|
};
|
|
52
82
|
}, [keys]);
|
|
53
83
|
(0, _element.useEffect)(() => {
|
|
84
|
+
if (!requestedSection.current) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!keys.includes(requestedSection.current)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
setActiveKey(requestedSection.current);
|
|
91
|
+
requestedSection.current = null;
|
|
92
|
+
}, [keys]);
|
|
93
|
+
(0, _element.useEffect)(() => {
|
|
94
|
+
if (requestedSection.current) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
54
97
|
if (keys.length && !keys.includes(activeKey)) {
|
|
55
98
|
setActiveKey(keys[0]);
|
|
56
99
|
}
|
|
57
100
|
}, [keys, activeKey]);
|
|
58
101
|
const selectSection = (0, _element.useCallback)(key => {
|
|
102
|
+
requestedSection.current = null;
|
|
59
103
|
setActiveKey(key);
|
|
60
104
|
window.history.replaceState(null, '', `#${key}`);
|
|
61
105
|
}, []);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dream-encode/wp-js-plugin-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Common JS functionality used by custom WP plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wordpress",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"./settings/styles": "./dist/settings/styles/_settings-page.scss",
|
|
28
28
|
"./settings/styles/rail": "./dist/settings/styles/_settings-rail.scss",
|
|
29
29
|
"./data-migrations": "./dist/data-migrations/index.js",
|
|
30
|
+
"./edit-product": "./dist/edit-product/index.js",
|
|
30
31
|
"./components": "./dist/components/index.js",
|
|
31
32
|
"./components/notifications-drawer/styles": "./dist/components/NotificationsDrawer/styles/_notifications-drawer.scss",
|
|
32
33
|
"./components/process/styles": "./dist/components/Process/styles/_process.scss",
|