@joystick.js/ui-canary 0.0.0-canary.25 → 0.0.0-canary.251
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/build.js +15 -0
- package/dist/index.js +20 -20
- package/increment_version.js +3 -0
- package/index.html +7 -0
- package/package.json +9 -13
- package/src/accounts/authenticated.js +8 -0
- package/src/accounts/index.js +21 -0
- package/src/accounts/login.js +7 -0
- package/src/accounts/logout.js +8 -0
- package/src/accounts/recover_password.js +7 -0
- package/src/accounts/request.js +70 -0
- package/src/accounts/reset_password.js +7 -0
- package/src/accounts/signup.js +7 -0
- package/src/accounts/user.js +8 -0
- package/src/api/get.js +73 -0
- package/src/api/index.js +10 -0
- package/src/api/set.js +83 -0
- package/src/attach_joystick_to_window.js +31 -0
- package/src/cache/class.js +98 -0
- package/src/cache/index.js +5 -0
- package/src/component/class.js +361 -0
- package/src/component/css/compile.js +108 -0
- package/src/component/data/compile.js +44 -0
- package/src/component/data/fetch.js +12 -0
- package/src/component/data/load_data_from_window.js +13 -0
- package/src/component/dom/is_valid_attribute_name.js +11 -0
- package/src/component/events/attach_event_listener_for_element.js +32 -0
- package/src/component/events/serialize.js +12 -0
- package/src/component/index.js +17 -0
- package/src/component/methods/compile.js +28 -0
- package/src/component/props/compile.js +21 -0
- package/src/component/props/compile_default.js +21 -0
- package/src/component/register_component_options.js +44 -0
- package/src/component/render_methods/component.js +53 -0
- package/src/component/render_methods/component_ssr.js +22 -0
- package/src/component/render_methods/each.js +19 -0
- package/src/component/render_methods/i18n.js +50 -0
- package/src/component/render_methods/index.js +19 -0
- package/src/component/render_methods/when.js +20 -0
- package/src/component/state/compile.js +21 -0
- package/src/component/url/compile.js +39 -0
- package/src/component/validate_component_options/has_required_options.js +9 -0
- package/src/component/validate_component_options/index.js +16 -0
- package/src/component/validate_component_options/required_options.js +5 -0
- package/src/component/virtual_dom/diff.js +63 -0
- package/src/component/virtual_dom/diff_attributes.js +37 -0
- package/src/component/virtual_dom/diff_children.js +74 -0
- package/src/component/virtual_dom/element_patch_functions.js +31 -0
- package/src/component/virtual_dom/map_patch_functions_to_nodes.js +14 -0
- package/src/component/virtual_dom/render_virtual_dom_to_dom.js +37 -0
- package/src/external/get.js +9 -0
- package/src/external/track.js +10 -0
- package/src/forms/validate.js +306 -0
- package/src/forms/validators/credit_card.js +19 -0
- package/src/forms/validators/custom.js +5 -0
- package/src/forms/validators/email.js +12 -0
- package/src/forms/validators/equals.js +5 -0
- package/src/forms/validators/index.js +43 -0
- package/src/forms/validators/matches.js +5 -0
- package/src/forms/validators/max_length.js +5 -0
- package/src/forms/validators/min_length.js +5 -0
- package/src/forms/validators/phone.js +14 -0
- package/src/forms/validators/postal_code.js +204 -0
- package/src/forms/validators/regex.js +5 -0
- package/src/forms/validators/required.js +13 -0
- package/src/forms/validators/semver.js +14 -0
- package/src/forms/validators/slug.js +12 -0
- package/src/forms/validators/strong_password.js +12 -0
- package/src/forms/validators/url.js +12 -0
- package/src/forms/validators/vat.js +54 -0
- package/src/index.js +55 -0
- package/src/lib/constants.js +13 -0
- package/src/lib/debounce.js +9 -0
- package/src/lib/generate_cookie_header.js +7 -0
- package/src/lib/generate_id.js +15 -0
- package/src/lib/get_joystick_environment.js +13 -0
- package/src/lib/log_request_errors.js +15 -0
- package/src/lib/nested_object_diff.js +164 -0
- package/src/lib/parse_json.js +12 -0
- package/src/lib/throw_framework_error.js +5 -0
- package/src/lib/types.js +59 -0
- package/src/mount/index.js +45 -0
- package/src/test/create_file.js +10 -0
- package/src/test/index.js +13 -0
- package/src/test/track_function_call.js +16 -0
- package/src/tree/add_node_to_tree.js +6 -0
- package/src/tree/clean_up.js +11 -0
- package/src/tree/get_child_instance_ids.js +7 -0
- package/src/tree/get_children_from_tree.js +25 -0
- package/src/tree/get_css_from_tree.js +43 -0
- package/src/tree/get_event_listeners_for_nodes.js +15 -0
- package/src/tree/get_node_from_tree.js +7 -0
- package/src/tree/get_parent_from_tree.js +8 -0
- package/src/tree/jobs/index.js +196 -0
- package/src/tree/jobs/run.js +11 -0
- package/src/tree/replace_child_in_vdom.js +18 -0
- package/src/upload/index.js +72 -0
- package/src/websockets/client.js +125 -0
- package/src/websockets/register_on_component.js +27 -0
- package/test.js +145 -0
- package/README.md +0 -8
- package/_package.json +0 -26
- package/canary.js +0 -12
- package/dist/accounts/authenticated.js +0 -1
- package/dist/accounts/index.js +0 -1
- package/dist/accounts/login.js +0 -1
- package/dist/accounts/logout.js +0 -1
- package/dist/accounts/recoverPassword.js +0 -1
- package/dist/accounts/request.js +0 -1
- package/dist/accounts/resetPassword.js +0 -1
- package/dist/accounts/signup.js +0 -1
- package/dist/accounts/user.js +0 -1
- package/dist/api/get.js +0 -1
- package/dist/api/index.js +0 -1
- package/dist/api/set.js +0 -1
- package/dist/attachJoystickToWindow.js +0 -1
- package/dist/cache/class.js +0 -1
- package/dist/cache/index.js +0 -1
- package/dist/component/class/css/compile.js +0 -21
- package/dist/component/class/css/prefix.js +0 -1
- package/dist/component/class/data/compile.js +0 -1
- package/dist/component/class/data/fetch.js +0 -1
- package/dist/component/class/data/findComponentDataFromSSR.js +0 -1
- package/dist/component/class/data/loadDataFromWindow.js +0 -1
- package/dist/component/class/events/_registerListeners.js +0 -1
- package/dist/component/class/events/_unregisterListeners.js +0 -1
- package/dist/component/class/events/registerListeners.js +0 -1
- package/dist/component/class/events/serialize.js +0 -1
- package/dist/component/class/events/unregisterListeners.js +0 -1
- package/dist/component/class/index.js +0 -21
- package/dist/component/class/lifecycle/compile.js +0 -1
- package/dist/component/class/methods/compile.js +0 -1
- package/dist/component/class/options/allowedComponentOptions.js +0 -1
- package/dist/component/class/options/allowedDOMEvents.js +0 -1
- package/dist/component/class/options/allowedLifecycleMethods.js +0 -1
- package/dist/component/class/options/defaultLifecycleMethods.js +0 -1
- package/dist/component/class/options/hasAllRequiredOptions.js +0 -1
- package/dist/component/class/options/registerOptions.js +0 -21
- package/dist/component/class/options/requiredOptions.js +0 -1
- package/dist/component/class/options/validateOptions.js +0 -1
- package/dist/component/class/options/validators/css.js +0 -1
- package/dist/component/class/options/validators/events.js +0 -1
- package/dist/component/class/options/validators/index.js +0 -1
- package/dist/component/class/options/validators/lifecycle.js +0 -1
- package/dist/component/class/options/validators/methods.js +0 -1
- package/dist/component/class/options/validators/name.js +0 -1
- package/dist/component/class/options/validators/render.js +0 -1
- package/dist/component/class/options/validators/websockets.js +0 -1
- package/dist/component/class/options/validators/wrapper.js +0 -1
- package/dist/component/class/props/compile.js +0 -1
- package/dist/component/class/props/compileDefault.js +0 -1
- package/dist/component/class/render/clearTimersOnChildren.js +0 -1
- package/dist/component/class/render/forMount.js +0 -1
- package/dist/component/class/render/getExistingPropsMap.js +0 -1
- package/dist/component/class/render/getExistingStateMap.js +0 -1
- package/dist/component/class/render/getUpdatedDOM.js +0 -1
- package/dist/component/class/render/sanitizeHTML.js +0 -1
- package/dist/component/class/render/toHTML.js +0 -1
- package/dist/component/class/render/wrapHTML.js +0 -1
- package/dist/component/class/renderMethods/compile.js +0 -1
- package/dist/component/class/renderMethods/component.js +0 -1
- package/dist/component/class/renderMethods/dragDrop/attachEventListeners.js +0 -1
- package/dist/component/class/renderMethods/dragDrop/concept.js +0 -0
- package/dist/component/class/renderMethods/dragDrop/draggable.js +0 -1
- package/dist/component/class/renderMethods/dragDrop/droppable.js +0 -1
- package/dist/component/class/renderMethods/dragDrop/index.js +0 -1
- package/dist/component/class/renderMethods/dragDrop/isBefore.js +0 -1
- package/dist/component/class/renderMethods/each.js +0 -1
- package/dist/component/class/renderMethods/i18n.js +0 -1
- package/dist/component/class/renderMethods/index.js +0 -1
- package/dist/component/class/renderMethods/when.js +0 -1
- package/dist/component/class/state/compile.js +0 -1
- package/dist/component/class/url/compile.js +0 -1
- package/dist/component/class/virtualDOM/build.js +0 -1
- package/dist/component/class/virtualDOM/buildTree.js +0 -1
- package/dist/component/class/virtualDOM/diff/attributes.js +0 -1
- package/dist/component/class/virtualDOM/diff/children.js +0 -1
- package/dist/component/class/virtualDOM/diff/elementPatchFunctions.js +0 -1
- package/dist/component/class/virtualDOM/diff/index.js +0 -1
- package/dist/component/class/virtualDOM/diff/mapPatchFunctionsToNodes.js +0 -1
- package/dist/component/class/virtualDOM/renderTreeToDOM.js +0 -1
- package/dist/component/index.js +0 -21
- package/dist/component/renderComponentToHTML.js +0 -1
- package/dist/component/tree/addChildToParent.js +0 -1
- package/dist/component/tree/clearChildrenOnParent.js +0 -1
- package/dist/component/tree/findComponentInTreeByField.js +0 -1
- package/dist/component/tree/replaceChildInVDOMTree.js +0 -1
- package/dist/component/tree/updateParentInstanceInTree.js +0 -1
- package/dist/css/getCSSFromTree.js +0 -21
- package/dist/css/update.js +0 -21
- package/dist/forms/validate.js +0 -1
- package/dist/forms/validators/creditCard.js +0 -1
- package/dist/forms/validators/email.js +0 -1
- package/dist/forms/validators/equals.js +0 -1
- package/dist/forms/validators/index.js +0 -1
- package/dist/forms/validators/matches.js +0 -1
- package/dist/forms/validators/maxLength.js +0 -1
- package/dist/forms/validators/minLength.js +0 -1
- package/dist/forms/validators/phone.js +0 -1
- package/dist/forms/validators/postalCode.js +0 -1
- package/dist/forms/validators/required.js +0 -1
- package/dist/forms/validators/semVer.js +0 -1
- package/dist/forms/validators/slug.js +0 -1
- package/dist/forms/validators/strongPassword.js +0 -1
- package/dist/forms/validators/types.js +0 -1
- package/dist/forms/validators/url.js +0 -1
- package/dist/forms/validators/vat.js +0 -1
- package/dist/html/index.js +0 -1
- package/dist/lib/addToQueue.js +0 -1
- package/dist/lib/assignOptionsToInstance.js +0 -1
- package/dist/lib/constants.js +0 -1
- package/dist/lib/debounce.js +0 -1
- package/dist/lib/generateId.js +0 -1
- package/dist/lib/isNode.js +0 -1
- package/dist/lib/isValidAttributeName.js +0 -1
- package/dist/lib/logRequestErrors.js +0 -1
- package/dist/lib/parseJSON.js +0 -1
- package/dist/lib/processQueue.js +0 -1
- package/dist/lib/queueArray.js +0 -1
- package/dist/lib/serializeEvents.js +0 -1
- package/dist/lib/stringContainsHTML.js +0 -1
- package/dist/lib/throttle.js +0 -1
- package/dist/lib/throwFrameworkError.js +0 -1
- package/dist/lib/types.js +0 -1
- package/dist/lib/windowIsUndefined.js +0 -1
- package/dist/mount/appendToTarget.js +0 -1
- package/dist/mount/index.js +0 -1
- package/dist/mount/initializeJoystickComponentTree.js +0 -1
- package/dist/overrideTimers.js +0 -1
- package/dist/upload/index.js +0 -1
- package/dist/websockets/client.js +0 -1
package/src/api/set.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import generate_cookie_header from "../lib/generate_cookie_header.js";
|
|
2
|
+
import log_request_errors from "../lib/log_request_errors.js";
|
|
3
|
+
import parse_json from "../lib/parse_json.js";
|
|
4
|
+
|
|
5
|
+
const handle_parse_response = async (response = {}) => {
|
|
6
|
+
// NOTE: Parse as text and then pass to parse_json so Joystick can explicitly handle
|
|
7
|
+
// invalid JSON errors.
|
|
8
|
+
const response_as_text = await response.text();
|
|
9
|
+
const data_from_response = parse_json(response_as_text);
|
|
10
|
+
return data_from_response;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const get_body = (setter_options = {}) => {
|
|
14
|
+
const sanitized_setter_options = { ...setter_options };
|
|
15
|
+
|
|
16
|
+
if (sanitized_setter_options?.loader) {
|
|
17
|
+
delete sanitized_setter_options.loader;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return JSON.stringify(sanitized_setter_options);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const set = (setter_name = "", setter_options = {}) => {
|
|
24
|
+
if (typeof window.fetch !== 'undefined') {
|
|
25
|
+
// NOTE: Wrap fetch() with another Promise so we can control routing of errors
|
|
26
|
+
// received in the response (by default they don't go to catch() which is where
|
|
27
|
+
// set() should return errors).
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const url = `${window.location.origin}/api/_setters/${setter_name}`;
|
|
30
|
+
const body = get_body(setter_options);
|
|
31
|
+
const csrf = document.querySelector('[name="csrf"]')?.getAttribute('content');
|
|
32
|
+
|
|
33
|
+
const headers = {
|
|
34
|
+
...(setter_options?.headers || {}),
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
'x-joystick-csrf': csrf,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (window?.__joystick_test__) {
|
|
40
|
+
headers.Cookie = generate_cookie_header({
|
|
41
|
+
joystick_login_token: window?.__joystick_test_login_token__,
|
|
42
|
+
joystick_login_token_expires_at: window.__joystick_test_login_token_expires_at__,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (setter_options?.loader?.instance) {
|
|
47
|
+
setter_options?.loader?.instance?.setState({ [setter_options?.loader?.state || `${setter_name}_loading`]: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return fetch(url, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
mode: "cors",
|
|
53
|
+
headers,
|
|
54
|
+
body,
|
|
55
|
+
credentials: "include",
|
|
56
|
+
}).then(async (response) => {
|
|
57
|
+
const data_from_response = await handle_parse_response(response);
|
|
58
|
+
if (setter_options?.loader?.instance) {
|
|
59
|
+
setter_options?.loader?.instance?.setState({ [setter_options?.loader?.state || `${setter_name}_loading`]: false });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (data_from_response?.errors) {
|
|
63
|
+
log_request_errors(`Set request`, data_from_response.errors);
|
|
64
|
+
return reject(data_from_response);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return resolve(data_from_response);
|
|
68
|
+
}).catch((error) => {
|
|
69
|
+
log_request_errors(`Set request`, [error]);
|
|
70
|
+
|
|
71
|
+
if (setter_options?.loader?.instance) {
|
|
72
|
+
setter_options?.loader?.instance?.setState({ [setter_options?.loader?.state || `${setter_name}_loading`]: false });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return reject({ errors: [error] });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return Promise.resolve();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default set;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import throw_framework_error from './lib/throw_framework_error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Safely attach the joystick global to the browser window.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} joystick - The joystick global object.
|
|
7
|
+
*/
|
|
8
|
+
const attach_joystick_to_window = (joystick = {}) => {
|
|
9
|
+
let target = null;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// NOTE: Browser support.
|
|
13
|
+
if (typeof window !== 'undefined') {
|
|
14
|
+
target = window;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// NOTE: Node.js support (for tests).
|
|
18
|
+
if (typeof global !== 'undefined') {
|
|
19
|
+
target = global;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (target) {
|
|
23
|
+
target.joystick = {
|
|
24
|
+
...(target?.joystick || {}),
|
|
25
|
+
settings: target?.__joystick_settings__,
|
|
26
|
+
...joystick,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default attach_joystick_to_window;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import types from "../lib/types.js";
|
|
2
|
+
|
|
3
|
+
class Cache {
|
|
4
|
+
constructor(cache_name = '', default_value = {}) {
|
|
5
|
+
this.name = cache_name;
|
|
6
|
+
this.value = default_value || {};
|
|
7
|
+
this.listeners = {
|
|
8
|
+
change: [],
|
|
9
|
+
set: [],
|
|
10
|
+
unset: [],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_get_value_at_path(path = '') {
|
|
15
|
+
return path?.split('.').reduce((value = null, path_part = '') => {
|
|
16
|
+
if (value[path_part]) {
|
|
17
|
+
value = value[path_part];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return value;
|
|
21
|
+
}, this.value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get(path = '') {
|
|
25
|
+
if (path?.trim() === '') {
|
|
26
|
+
return {
|
|
27
|
+
...(this.value || {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this._get_value_at_path(path);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set(callback = null, user_event_label = '') {
|
|
35
|
+
if (!callback || !types.is_function(callback)) {
|
|
36
|
+
throw new Error('First argument passed to set() must be a function.');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const updated_value = callback(this.value);
|
|
40
|
+
|
|
41
|
+
if (!types.is_object(updated_value)) {
|
|
42
|
+
throw new Error('Value to set must be an object representing the current cache state.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.value = updated_value;
|
|
46
|
+
this._handle_event('set', user_event_label);
|
|
47
|
+
this._handle_event('change', user_event_label);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
unset(path = '', user_event_label = '') {
|
|
51
|
+
if (path?.trim() === '') {
|
|
52
|
+
this.value = {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// NOTE: Adapted from https://youmightnotneed.com/lodash#unset
|
|
56
|
+
const path_array = path?.split('.');
|
|
57
|
+
const key_to_delete_index = path_array?.length - 1;
|
|
58
|
+
|
|
59
|
+
path_array.reduce((updated_value, key, i) => {
|
|
60
|
+
if (i === key_to_delete_index) delete updated_value[key];
|
|
61
|
+
return updated_value[key];
|
|
62
|
+
}, this.value);
|
|
63
|
+
|
|
64
|
+
this._handle_event('unset', user_event_label);
|
|
65
|
+
this._handle_event('change', user_event_label);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
on(event = '', callback = null) {
|
|
69
|
+
if (!['change', 'set', 'unset'].includes(event)) {
|
|
70
|
+
throw new Error(`Event to listen for must be change, set, or unset. ${event} is not supported.`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!callback || !types.is_function(callback)) {
|
|
74
|
+
throw new Error('Second argument passed to on('<event>') must be a function.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.listeners = {
|
|
78
|
+
[event]: [
|
|
79
|
+
...(this.listeners[event] || []),
|
|
80
|
+
callback,
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_handle_event(internal_event_label = '', user_event_label = '') {
|
|
86
|
+
const listeners = this.listeners[internal_event_label] || [];
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < listeners?.length; i += 1) {
|
|
89
|
+
const listener = listeners[i];
|
|
90
|
+
|
|
91
|
+
if (types.is_function(listener)) {
|
|
92
|
+
listener(this.value, internal_event_label, user_event_label);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default Cache;
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import clean_up_tree from "../tree/clean_up.js";
|
|
2
|
+
import compile_state from "./state/compile.js";
|
|
3
|
+
import constants from "../lib/constants.js";
|
|
4
|
+
import debounce from "../lib/debounce.js";
|
|
5
|
+
import diff_virtual_dom from "./virtual_dom/diff.js";
|
|
6
|
+
import generate_id from "../lib/generate_id.js";
|
|
7
|
+
import get_child_instance_ids from "../tree/get_child_instance_ids.js";
|
|
8
|
+
import get_children_from_tree from "../tree/get_children_from_tree.js";
|
|
9
|
+
import register_component_options from "./register_component_options.js";
|
|
10
|
+
import render_methods from './render_methods/index.js';
|
|
11
|
+
import replace_child_in_vdom from "../tree/replace_child_in_vdom.js";
|
|
12
|
+
import run_tree_job from "../tree/jobs/run.js";
|
|
13
|
+
import track_function_call from "../test/track_function_call.js";
|
|
14
|
+
import types from "../lib/types.js";
|
|
15
|
+
|
|
16
|
+
class Component {
|
|
17
|
+
constructor(component_options = {}) {
|
|
18
|
+
register_component_options(this, component_options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
cleanup_html(html = '', linkedom_document = null) {
|
|
22
|
+
const div = (typeof document === 'undefined' ? linkedom_document : document).createElement('div');
|
|
23
|
+
div.innerHTML = html;
|
|
24
|
+
return div.innerHTML;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
compile_render_methods(new_children = {}, existing_children = {}, ssr_tree = null) {
|
|
28
|
+
return Object.entries(render_methods).reduce((methods, [key, value]) => {
|
|
29
|
+
let instance_to_bind = {
|
|
30
|
+
...(this || {}),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (key === 'component') {
|
|
34
|
+
instance_to_bind = {
|
|
35
|
+
...(this || {}),
|
|
36
|
+
new_children,
|
|
37
|
+
existing_children,
|
|
38
|
+
ssr_tree: ssr_tree ? {
|
|
39
|
+
push: (node) => {
|
|
40
|
+
ssr_tree.push(node);
|
|
41
|
+
},
|
|
42
|
+
} : null,
|
|
43
|
+
track_child: !ssr_tree ? (child_component_instance = {}) => {
|
|
44
|
+
const existing_component_id = new_children[child_component_instance?.id];
|
|
45
|
+
|
|
46
|
+
if (existing_component_id) {
|
|
47
|
+
existing_component_id.push(child_component_instance?.instance_id);
|
|
48
|
+
} else {
|
|
49
|
+
new_children[child_component_instance?.id] = [child_component_instance?.instance_id];
|
|
50
|
+
}
|
|
51
|
+
} : null,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (key !== 'component_ssr') {
|
|
56
|
+
// NOTE: When rendering for SSR, swap the normal component render method with the SSR version.
|
|
57
|
+
const render_method_to_bind = key === 'component' && !!ssr_tree ? render_methods.component_ssr : value;
|
|
58
|
+
methods[key] = render_method_to_bind.bind(instance_to_bind);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return methods;
|
|
62
|
+
}, {});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async fetch_data(api = {}, req = {}, input = {}, component_instance = this) {
|
|
66
|
+
if (component_instance?.options?.data && types.is_function(component_instance.options.data)) {
|
|
67
|
+
const data = await component_instance.options.data(api, req, input, component_instance);
|
|
68
|
+
component_instance.data = data;
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
render_child_dom_node_to_virtual_dom(dom_node = {}) {
|
|
76
|
+
if (dom_node.tagName === 'WHEN') {
|
|
77
|
+
return [].flatMap.call(dom_node?.childNodes || [], (child_node) => {
|
|
78
|
+
if (child_node?.tagName === 'WHEN') {
|
|
79
|
+
return this.render_child_dom_node_to_virtual_dom(child_node);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return this.render_dom_to_virtual_dom(child_node);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return this.render_dom_to_virtual_dom(dom_node);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
parse_attributes(attributes = {}) {
|
|
90
|
+
return Object.values(attributes)
|
|
91
|
+
.reduce((parsed_attributes = {}, attribute) => {
|
|
92
|
+
parsed_attributes[attribute.name] = attribute.value;
|
|
93
|
+
return parsed_attributes;
|
|
94
|
+
}, {});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
render_for_mount() {
|
|
98
|
+
track_function_call(`ui.${this?.options?.test?.name || generate_id()}.render_for_mount`, []);
|
|
99
|
+
|
|
100
|
+
// NOTE: Pass both new_children and existing_children for consistency,
|
|
101
|
+
// even though existing_children don't exist at mount time.
|
|
102
|
+
const new_children = {};
|
|
103
|
+
const existing_children = {};
|
|
104
|
+
const component_html = this.render_to_html(new_children, existing_children);
|
|
105
|
+
const html = this.replace_when_tags(component_html);
|
|
106
|
+
const dom = this.render_html_to_dom(html);
|
|
107
|
+
const virtual_dom = this.render_dom_to_virtual_dom(dom);
|
|
108
|
+
|
|
109
|
+
this.dom = dom;
|
|
110
|
+
this.DOMNode = dom;
|
|
111
|
+
this.virtual_dom = virtual_dom;
|
|
112
|
+
this.children = new_children;
|
|
113
|
+
|
|
114
|
+
this.sync_children_to_parent(this.children, this.dom, this.virtual_dom);
|
|
115
|
+
|
|
116
|
+
return dom;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async render_for_ssr(api = {}, req = {}, ssr_tree = [], render_for_ssr_options = {}) {
|
|
120
|
+
return new Promise(async (resolve) => {
|
|
121
|
+
// NOTE: Fetch data for this component before rendering to HTML so the render() method has
|
|
122
|
+
// access to the data at render time.
|
|
123
|
+
const component_data = await this.fetch_data(api, req, {}, this);
|
|
124
|
+
const new_children = {};
|
|
125
|
+
const existing_children = {};
|
|
126
|
+
let component_html = this.render_to_html(new_children, existing_children, ssr_tree, render_for_ssr_options?.linkedom_document);
|
|
127
|
+
const child_data = {};
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < ssr_tree?.length; i += 1) {
|
|
130
|
+
const node = ssr_tree[i];
|
|
131
|
+
const node_data = await this.fetch_data(api, req, {}, node);
|
|
132
|
+
const node_html = node.render_to_html(new_children, existing_children, ssr_tree, render_for_ssr_options?.linkedom_document);
|
|
133
|
+
component_html = component_html.replace(`{{${node.id}:${node.instance_id}}}`, node_html);
|
|
134
|
+
child_data[node?.id] = node_data;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ssr_tree.push(this);
|
|
138
|
+
|
|
139
|
+
const html = this.replace_when_tags(component_html);
|
|
140
|
+
const css = run_tree_job('css', { ssr_tree, is_email: render_for_ssr_options?.is_email || false });
|
|
141
|
+
|
|
142
|
+
resolve({
|
|
143
|
+
html,
|
|
144
|
+
css,
|
|
145
|
+
data: {
|
|
146
|
+
[this.id]: component_data,
|
|
147
|
+
...(child_data || {}),
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
render_html_to_dom(html = '') {
|
|
154
|
+
const template = document.createElement('template');
|
|
155
|
+
template.innerHTML = html;
|
|
156
|
+
return template?.content?.firstChild;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
replace_when_tags(html = '') {
|
|
160
|
+
return html;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
render_to_html(new_children = {}, existing_children = {}, ssr_tree = null, linkedom_document = {}) {
|
|
164
|
+
const render_methods = this.compile_render_methods(new_children, existing_children, ssr_tree);
|
|
165
|
+
const html = this.options.render({ ...(this || {}), ...render_methods });
|
|
166
|
+
const clean_html = this.cleanup_html(html, linkedom_document);
|
|
167
|
+
const sanitized_html = this.sanitize_html(clean_html);
|
|
168
|
+
const wrapped_html = this.wrap_html(sanitized_html);
|
|
169
|
+
return wrapped_html;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
render_dom_to_virtual_dom(dom_node = {}) {
|
|
173
|
+
console.log({ dom_node, has_tagName: !!dom_node?.tagName, has_tag_name: !!dom_node?.tag_name });
|
|
174
|
+
const tag_name = (dom_node && dom_node.tagName && dom_node.tagName.toLowerCase()) || 'text';
|
|
175
|
+
|
|
176
|
+
if (tag_name === 'text') {
|
|
177
|
+
return dom_node.textContent;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const virtual_dom_node = {
|
|
181
|
+
tag_name,
|
|
182
|
+
attributes: this.parse_attributes(dom_node?.attributes),
|
|
183
|
+
children: [].flatMap.call(dom_node?.childNodes || [], (child_node) => {
|
|
184
|
+
return this.render_child_dom_node_to_virtual_dom(child_node);
|
|
185
|
+
}),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const component_id = dom_node?.getAttribute('js-c');
|
|
189
|
+
const instance_id = dom_node?.getAttribute('js-i');
|
|
190
|
+
|
|
191
|
+
if (component_id) {
|
|
192
|
+
virtual_dom_node.component_id = component_id;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (instance_id) {
|
|
196
|
+
virtual_dom_node.instance_id = instance_id;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return virtual_dom_node;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
rerender(options = {}) {
|
|
203
|
+
track_function_call(`ui.${this?.options?.test?.name || generate_id()}.rerender`, [
|
|
204
|
+
options,
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
// run_tree_job('clear_timers', { root_instance_id: this?.instance_id });
|
|
208
|
+
run_tree_job('lifecycle.onBeforeRender', { root_instance_id: this?.instance_id });
|
|
209
|
+
|
|
210
|
+
const new_children = {};
|
|
211
|
+
const existing_children = { ...(this.children || {}) };
|
|
212
|
+
|
|
213
|
+
// NOTE: Once we have a copy of current children as existing_children
|
|
214
|
+
// in memory, dump them from the parent instance.
|
|
215
|
+
this.children = {};
|
|
216
|
+
|
|
217
|
+
// NOTE: Unregister listeners for this root node and its children as they're
|
|
218
|
+
// going to be removed and re-rendered.
|
|
219
|
+
run_tree_job('detach_event_listeners', { root_instance_id: this?.instance_id });
|
|
220
|
+
|
|
221
|
+
// NOTE: As part of render_to_html(), we expect new_children to be populated
|
|
222
|
+
// with the new child instance_ids via the track_child method we pass to the
|
|
223
|
+
// render methods via the .bind() to the parent in compile_render_methods.
|
|
224
|
+
const component_html = this.render_to_html(new_children, existing_children);
|
|
225
|
+
const new_html = this.replace_when_tags(component_html);
|
|
226
|
+
const new_dom = this.render_html_to_dom(new_html);
|
|
227
|
+
const new_virtual_dom = this.render_dom_to_virtual_dom(new_dom);
|
|
228
|
+
const dom_node_patches = diff_virtual_dom(this.virtual_dom, new_virtual_dom);
|
|
229
|
+
|
|
230
|
+
if (types.is_function(dom_node_patches)) {
|
|
231
|
+
const patched_dom_node = dom_node_patches(this.dom);
|
|
232
|
+
|
|
233
|
+
this.dom = patched_dom_node;
|
|
234
|
+
this.virtual_dom = new_virtual_dom;
|
|
235
|
+
|
|
236
|
+
// NOTE: After all tree-related work is done, set the new children back on the
|
|
237
|
+
// parent instance. Doing this here ensures any tree jobs don't get tripped up
|
|
238
|
+
// by children they don't recognize.
|
|
239
|
+
this.children = new_children;
|
|
240
|
+
this.sync_children_to_parent(this.children, this.dom, this.virtual_dom);
|
|
241
|
+
|
|
242
|
+
// NOTE: Set DOMNode after children are sync'd so this.DOMNode is kept fresh.
|
|
243
|
+
this.DOMNode = patched_dom_node;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
run_tree_job('attach_event_listeners', { root_instance_id: this?.instance_id });
|
|
247
|
+
run_tree_job('lifecycle.onRender', { root_instance_id: this?.instance_id });
|
|
248
|
+
|
|
249
|
+
if (types.is_function(options?.after_set_state_rerender)) {
|
|
250
|
+
options.after_set_state_rerender();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (types.is_function(options?.after_refetch_data_rerender)) {
|
|
254
|
+
options.after_refetch_data_rerender();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// NOTE: Clean up the linked list by removing any nodes matching an ID
|
|
258
|
+
// in existing_children as we know they no longer exist.
|
|
259
|
+
clean_up_tree(existing_children);
|
|
260
|
+
|
|
261
|
+
// NOTE: Do after clean up so we don't reattach styles for old nodes.
|
|
262
|
+
run_tree_job('css');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
sanitize_html(html = '') {
|
|
266
|
+
const html_comments_to_replace = html.match(constants.JOYSTICK_COMMENT_REGEX) || [];
|
|
267
|
+
|
|
268
|
+
for (let i = 0; i < html_comments_to_replace?.length; i += 1) {
|
|
269
|
+
html = html.replace(html_comments_to_replace[i], '');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return html;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
set_interval(callback = null, delay = 0) {
|
|
276
|
+
this.setInterval(callback, delay);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
setInterval(callback = null, delay = 0) {
|
|
280
|
+
track_function_call(`ui.${this?.options?.test?.name || generate_id()}.set_interval`, [
|
|
281
|
+
callback,
|
|
282
|
+
delay,
|
|
283
|
+
]);
|
|
284
|
+
|
|
285
|
+
if (callback) {
|
|
286
|
+
this.timers.push(window.setInterval(callback, delay));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
set_state(state = {}, callback = null) {
|
|
291
|
+
return this.setState(state, callback);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
setState(state = {}, callback = null) {
|
|
295
|
+
track_function_call(`ui.${this?.options?.test?.name || generate_id()}.set_state`, [
|
|
296
|
+
state,
|
|
297
|
+
callback,
|
|
298
|
+
]);
|
|
299
|
+
|
|
300
|
+
this.state = compile_state(this, {
|
|
301
|
+
...(this.state || {}),
|
|
302
|
+
...state,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// NOTE: Use a 0ms debounce here to prevent rapid setState() calls from blocking the
|
|
306
|
+
// DOM from rendering nodes related to state changes.
|
|
307
|
+
this.rerender({
|
|
308
|
+
after_set_state_rerender: () => {
|
|
309
|
+
if (callback && types.is_function(callback)) {
|
|
310
|
+
callback();
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
set_timeout(callback = null, delay = 0) {
|
|
317
|
+
this.setTimeout(callback, delay);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
setTimeout(callback = null, delay = 0) {
|
|
321
|
+
track_function_call(`ui.${this?.options?.test?.name || generate_id()}.set_timeout`, [
|
|
322
|
+
callback,
|
|
323
|
+
delay,
|
|
324
|
+
]);
|
|
325
|
+
|
|
326
|
+
if (callback) {
|
|
327
|
+
this.timers.push(window.setTimeout(callback, delay));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
sync_children_to_parent(children = {}, dom = {}, virtual_dom = {}) {
|
|
332
|
+
if (dom) {
|
|
333
|
+
const child_instance_ids = get_child_instance_ids(children);
|
|
334
|
+
const child_nodes = get_children_from_tree(child_instance_ids);
|
|
335
|
+
|
|
336
|
+
// NOTE: Because we ultimately mount the parent's DOM node to the screen, we want to ensure
|
|
337
|
+
// that children of the parent reference the DOM node the *parent* created, not the one the
|
|
338
|
+
// child created when it rendered itself (that only exists temporarily to get back the HTML
|
|
339
|
+
// for the child at render time).
|
|
340
|
+
for (let i = 0; i < child_nodes?.length; i += 1) {
|
|
341
|
+
const child_node = child_nodes[i];
|
|
342
|
+
const child_node_in_parent_dom = dom.querySelector(`[js-i="${child_node?.instance_id}"]`);
|
|
343
|
+
|
|
344
|
+
child_node.dom = child_node_in_parent_dom;
|
|
345
|
+
child_node.DOMNode = child_node_in_parent_dom;
|
|
346
|
+
|
|
347
|
+
replace_child_in_vdom(virtual_dom, child_node?.instance_id, child_node?.virtual_dom);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
wrap_html(html = '') {
|
|
353
|
+
const wrapper_tag = (this.options?.wrapper?.tagName || this.options?.wrapper?.tag_name)?.toLowerCase() || 'div';
|
|
354
|
+
const wrapper_id = this.options?.wrapper?.id || null;
|
|
355
|
+
const wrapper_class_list = (this.options?.wrapper?.classList || this.options?.wrapper?.class_list)?.join(' ') || '';
|
|
356
|
+
|
|
357
|
+
return `<${wrapper_tag} ${wrapper_id ? `id="${wrapper_id}"` : ''} ${wrapper_class_list ? `class="${wrapper_class_list}"` : ''} js-c="${this.id}" js-i="${this.instance_id}">${html}</${wrapper_tag}>`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export default Component;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import types from "../../lib/types.js";
|
|
2
|
+
|
|
3
|
+
const compile = (css = "", component_instance = {}) => {
|
|
4
|
+
let compiled_css = "";
|
|
5
|
+
|
|
6
|
+
if (css && types.is_string(css)) {
|
|
7
|
+
compiled_css = css;
|
|
8
|
+
return compiled_css;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (css && types.is_function(css)) {
|
|
12
|
+
compiled_css = css(component_instance);
|
|
13
|
+
return compiled_css;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (css && types.is_object(css)) {
|
|
17
|
+
const has_print_rules =
|
|
18
|
+
css?.print && (types.is_string(css?.print) || types.is_function(css?.print));
|
|
19
|
+
const has_min_rules = css?.min && types.is_object(css?.min);
|
|
20
|
+
const has_min_width_rules = css?.min?.width && types.is_object(css?.min?.width);
|
|
21
|
+
const has_min_height_rules = css?.min?.height && types.is_object(css?.min?.height);
|
|
22
|
+
const has_max_rules = css?.max && types.is_object(css?.max);
|
|
23
|
+
const has_max_width_rules = css?.max?.width && types.is_object(css?.max?.width);
|
|
24
|
+
const has_max_height_rules = css?.max?.height && types.is_object(css?.max?.height);
|
|
25
|
+
|
|
26
|
+
if (has_print_rules) {
|
|
27
|
+
compiled_css += `
|
|
28
|
+
@media print {
|
|
29
|
+
${
|
|
30
|
+
typeof css?.print === "function"
|
|
31
|
+
? css?.print(component_instance)
|
|
32
|
+
: css.print
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (has_min_rules && has_min_width_rules) {
|
|
39
|
+
const min_width_rules = Object.entries(css?.min?.width);
|
|
40
|
+
for (let i = 0; i < min_width_rules.length; i += 1) {
|
|
41
|
+
const [min_width, min_width_rule] = min_width_rules[i];
|
|
42
|
+
compiled_css += `
|
|
43
|
+
@media screen and (min-width: ${min_width}px) {
|
|
44
|
+
${
|
|
45
|
+
typeof min_width_rule === "function"
|
|
46
|
+
? min_width_rule(component_instance)
|
|
47
|
+
: min_width_rule
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (has_min_rules && has_min_height_rules) {
|
|
55
|
+
const min_height_rules = Object.entries(css?.min?.height);
|
|
56
|
+
for (let i = 0; i < min_height_rules.length; i += 1) {
|
|
57
|
+
const [min_height, min_height_rule] = min_height_rules[i];
|
|
58
|
+
compiled_css += `
|
|
59
|
+
@media screen and (min-height: ${min_height}px) {
|
|
60
|
+
${
|
|
61
|
+
typeof min_height_rule === "function"
|
|
62
|
+
? min_height_rule(component_instance)
|
|
63
|
+
: min_height_rule
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (has_max_rules && has_max_width_rules) {
|
|
71
|
+
const max_width_rules = Object.entries(css?.max?.width);
|
|
72
|
+
for (let i = 0; i < max_width_rules.length; i += 1) {
|
|
73
|
+
const [max_width, max_width_rule] = max_width_rules[i];
|
|
74
|
+
compiled_css += `
|
|
75
|
+
@media screen and (max-width: ${max_width}px) {
|
|
76
|
+
${
|
|
77
|
+
typeof max_width_rule === "function"
|
|
78
|
+
? max_width_rule(component_instance)
|
|
79
|
+
: max_width_rule
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (has_max_rules && has_max_height_rules) {
|
|
87
|
+
const max_height_rules = Object.entries(css?.max?.height);
|
|
88
|
+
for (let i = 0; i < max_height_rules.length; i += 1) {
|
|
89
|
+
const [max_height, max_height_rule] = max_height_rules[i];
|
|
90
|
+
compiled_css += `
|
|
91
|
+
@media screen and (max-height: ${max_height}px) {
|
|
92
|
+
${
|
|
93
|
+
typeof max_height_rule === "function"
|
|
94
|
+
? max_height_rule(component_instance)
|
|
95
|
+
: max_height_rule
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return compiled_css;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return "";
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default compile;
|