@tsrx/core 0.0.1

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,26 @@
1
+ const ATTR_REGEX = /[&"<]/g;
2
+ const CONTENT_REGEX = /[&<]/g;
3
+
4
+ /**
5
+ * @template V
6
+ * @param {V} value
7
+ * @param {boolean} [is_attr]
8
+ */
9
+ export function escape(value, is_attr) {
10
+ const str = String(value ?? '');
11
+
12
+ const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
13
+ pattern.lastIndex = 0;
14
+
15
+ let escaped = '';
16
+ let last = 0;
17
+
18
+ while (pattern.test(str)) {
19
+ const i = pattern.lastIndex - 1;
20
+ const ch = str[i];
21
+ escaped += str.substring(last, i) + (ch === '&' ? '&amp;' : ch === '"' ? '&quot;' : '&lt;');
22
+ last = i + 1;
23
+ }
24
+
25
+ return escaped + str.substring(last);
26
+ }
@@ -0,0 +1,154 @@
1
+ /** @import { AddEventObject } from '../../types/index'*/
2
+
3
+ const NON_DELEGATED_EVENTS = new Set([
4
+ 'abort',
5
+ 'afterprint',
6
+ 'beforeprint',
7
+ 'beforetoggle',
8
+ 'beforeunload',
9
+ 'blur',
10
+ 'close',
11
+ 'command',
12
+ 'contextmenu',
13
+ 'cuechange',
14
+ 'DOMContentLoaded',
15
+ 'error',
16
+ 'focus',
17
+ 'invalid',
18
+ 'load',
19
+ 'loadend',
20
+ 'loadstart',
21
+ 'mouseenter',
22
+ 'mouseleave',
23
+ 'pointerenter',
24
+ 'pointerleave',
25
+ 'progress',
26
+ 'readystatechange',
27
+ 'resize',
28
+ 'scroll',
29
+ 'scrollend',
30
+ 'toggle',
31
+ 'unload',
32
+ 'visibilitychange',
33
+ // Media Events
34
+ 'canplay',
35
+ 'canplaythrough',
36
+ 'durationchange',
37
+ 'emptied',
38
+ 'encrypted',
39
+ 'ended',
40
+ 'loadeddata',
41
+ 'loadedmetadata',
42
+ 'loadstart',
43
+ 'pause',
44
+ 'play',
45
+ 'playing',
46
+ 'progress',
47
+ 'ratechange',
48
+ 'seeked',
49
+ 'seeking',
50
+ 'stalled',
51
+ 'suspend',
52
+ 'timeupdate',
53
+ 'volumechange',
54
+ 'waiting',
55
+ 'waitingforkey',
56
+ ]);
57
+
58
+ /**
59
+ * Checks if an event should be delegated
60
+ * @param {string} event_name - The event name (e.g., 'click', 'focus')
61
+ * @returns {boolean}
62
+ */
63
+ export function is_non_delegated(event_name) {
64
+ return NON_DELEGATED_EVENTS.has(event_name);
65
+ }
66
+
67
+ /**
68
+ * Determines if an attribute is an event attribute (e.g., 'onClick').
69
+ * @param {string} attr - The attribute name.
70
+ * @returns {boolean}
71
+ */
72
+ export function is_event_attribute(attr) {
73
+ return attr.startsWith('on') && attr.length > 2 && attr[2] === attr[2].toUpperCase();
74
+ }
75
+
76
+ /**
77
+ * Checks if the event is a capture event.
78
+ * @param {string} event_name - The event name.
79
+ * @returns {boolean}
80
+ */
81
+ export function is_capture_event(event_name) {
82
+ var lowered = event_name.toLowerCase();
83
+ return (
84
+ event_name.endsWith('Capture') &&
85
+ lowered !== 'gotpointercapture' &&
86
+ lowered !== 'lostpointercapture'
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Retrieves the original event name from an event attribute.
92
+ * @param {string} name
93
+ * @returns {string}
94
+ */
95
+ export function get_original_event_name(name) {
96
+ return name.slice(2);
97
+ }
98
+
99
+ /**
100
+ * Normalizes the event name to lowercase.
101
+ * @param {string} name
102
+ * @returns {string}
103
+ */
104
+ export function normalize_event_name(name) {
105
+ return extract_event_name(name).toLowerCase();
106
+ }
107
+
108
+ /**
109
+ * Extracts the base event name from an event attribute.
110
+ * @param {string} name
111
+ * @returns {string}
112
+ */
113
+ function extract_event_name(name) {
114
+ name = get_original_event_name(name);
115
+
116
+ if (is_capture_event(name)) {
117
+ return event_name_from_capture(name);
118
+ }
119
+ return name;
120
+ }
121
+
122
+ /**
123
+ * Converts a capture event name to its base event name.
124
+ * @param {string} event_name
125
+ * @returns {string}
126
+ */
127
+ export function event_name_from_capture(event_name) {
128
+ return event_name.slice(0, -7); // strip "Capture"
129
+ }
130
+
131
+ /**
132
+ * Converts an event attribute name to the actual event name.
133
+ * @param {string} name
134
+ * @param {EventListener | AddEventObject} handler
135
+ * @returns {string}
136
+ */
137
+ export function get_attribute_event_name(name, handler) {
138
+ name = extract_event_name(name);
139
+
140
+ return typeof handler === 'object' && handler.customName
141
+ ? handler.customName
142
+ : name.toLowerCase();
143
+ }
144
+
145
+ const PASSIVE_EVENTS = ['touchstart', 'touchmove', 'wheel', 'mousewheel'];
146
+
147
+ /**
148
+ * Checks if an event is passive (e.g., 'touchstart', 'touchmove').
149
+ * @param {string} name - The event name.
150
+ * @returns {boolean}
151
+ */
152
+ export function is_passive_event(name) {
153
+ return PASSIVE_EVENTS.includes(name);
154
+ }
@@ -0,0 +1,15 @@
1
+ const regex_return_characters = /\r/g;
2
+
3
+ /**
4
+ * Hashes a string to a base36 value
5
+ * @param {string} str
6
+ * @returns {string}
7
+ */
8
+ export function hash(str) {
9
+ str = str.replace(regex_return_characters, '');
10
+ let hash = 5381;
11
+ let i = str.length;
12
+
13
+ while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
14
+ return (hash >>> 0).toString(36);
15
+ }
@@ -0,0 +1,23 @@
1
+ /** @type {Map<string, string>} */
2
+ const normalized_properties_cache = new Map();
3
+
4
+ /**
5
+ * Takes a camelCased string and returns a hyphenated string
6
+ * @param {string} str
7
+ * @returns {string}
8
+ * @example
9
+ * normalize_css_property_name('backgroundColor') // 'background-color'
10
+ */
11
+ export function normalize_css_property_name(str) {
12
+ if (str.startsWith('--')) return str;
13
+
14
+ let normalized_result = normalized_properties_cache.get(str);
15
+ if (normalized_result != null) {
16
+ return normalized_result;
17
+ }
18
+
19
+ normalized_result = str.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
20
+ normalized_properties_cache.set(str, normalized_result);
21
+
22
+ return normalized_result;
23
+ }
@@ -0,0 +1,24 @@
1
+ export const regex_whitespace = /\s/;
2
+ export const regex_whitespaces = /\s+/;
3
+ export const regex_starts_with_newline = /^\r?\n/;
4
+ export const regex_starts_with_whitespace = /^\s/;
5
+ export const regex_starts_with_whitespaces = /^[ \t\r\n]+/;
6
+ export const regex_ends_with_whitespace = /\s$/;
7
+ export const regex_ends_with_whitespaces = /[ \t\r\n]+$/;
8
+ /** Not \S because that also removes explicit whitespace defined through things like `&nbsp;` */
9
+ export const regex_not_whitespace = /[^ \t\r\n]/;
10
+ /** Not \s+ because that also includes explicit whitespace defined through things like `&nbsp;` */
11
+ export const regex_whitespaces_strict = /[ \t\n\r\f]+/g;
12
+
13
+ export const regex_only_whitespaces = /^[ \t\n\r\f]+$/;
14
+
15
+ export const regex_newline_characters = /\n/g;
16
+ export const regex_not_newline_characters = /[^\n]/g;
17
+
18
+ export const regex_is_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
19
+ // used in replace all to remove all invalid chars from a literal identifier
20
+ export const regex_invalid_identifier_chars = /(^[^a-zA-Z_$]|[^a-zA-Z0-9_$])/g;
21
+
22
+ export const regex_starts_with_vowel = /^[aeiou]/;
23
+ export const regex_heading_tags = /^h[1-6]$/;
24
+ export const regex_illegal_attribute_character = /(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @param {string | null | undefined} str
3
+ * @returns {string}
4
+ */
5
+ export function sanitize_template_string(str) {
6
+ return (str ?? '').replace(/(`|\${|\\)/g, '\\$1');
7
+ }
package/src/utils.js ADDED
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Generic compiler utilities for tsrx-based frameworks.
3
+ * Framework-specific utilities should be in the framework package.
4
+ */
5
+
6
+ export { hash } from './utils/hashing.js';
7
+
8
+ const VOID_ELEMENT_NAMES = [
9
+ 'area',
10
+ 'base',
11
+ 'br',
12
+ 'col',
13
+ 'command',
14
+ 'embed',
15
+ 'hr',
16
+ 'img',
17
+ 'input',
18
+ 'keygen',
19
+ 'link',
20
+ 'meta',
21
+ 'param',
22
+ 'source',
23
+ 'track',
24
+ 'wbr',
25
+ ];
26
+
27
+ /**
28
+ * Returns true if name is a void element
29
+ * @param {string} name
30
+ * @returns {boolean}
31
+ */
32
+ export function is_void_element(name) {
33
+ return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype';
34
+ }
35
+
36
+ const RESERVED_WORDS = [
37
+ 'arguments',
38
+ 'await',
39
+ 'break',
40
+ 'case',
41
+ 'catch',
42
+ 'class',
43
+ 'const',
44
+ 'continue',
45
+ 'debugger',
46
+ 'default',
47
+ 'delete',
48
+ 'do',
49
+ 'else',
50
+ 'enum',
51
+ 'eval',
52
+ 'export',
53
+ 'extends',
54
+ 'false',
55
+ 'finally',
56
+ 'for',
57
+ 'function',
58
+ 'if',
59
+ 'implements',
60
+ 'import',
61
+ 'in',
62
+ 'instanceof',
63
+ 'interface',
64
+ 'let',
65
+ 'new',
66
+ 'null',
67
+ 'package',
68
+ 'private',
69
+ 'protected',
70
+ 'public',
71
+ 'return',
72
+ 'static',
73
+ 'super',
74
+ 'switch',
75
+ 'this',
76
+ 'throw',
77
+ 'true',
78
+ 'try',
79
+ 'typeof',
80
+ 'var',
81
+ 'void',
82
+ 'while',
83
+ 'with',
84
+ 'yield',
85
+ ];
86
+
87
+ /**
88
+ * Returns true if word is a reserved JS keyword
89
+ * @param {string} word
90
+ * @returns {boolean}
91
+ */
92
+ export function is_reserved(word) {
93
+ return RESERVED_WORDS.includes(word);
94
+ }
95
+
96
+ /**
97
+ * Attributes that are boolean, i.e. they are present or not present.
98
+ */
99
+ const DOM_BOOLEAN_ATTRIBUTES = [
100
+ 'allowfullscreen',
101
+ 'async',
102
+ 'autofocus',
103
+ 'autoplay',
104
+ 'checked',
105
+ 'controls',
106
+ 'default',
107
+ 'disabled',
108
+ 'formnovalidate',
109
+ 'hidden',
110
+ 'indeterminate',
111
+ 'inert',
112
+ 'ismap',
113
+ 'loop',
114
+ 'multiple',
115
+ 'muted',
116
+ 'nomodule',
117
+ 'novalidate',
118
+ 'open',
119
+ 'playsinline',
120
+ 'readonly',
121
+ 'required',
122
+ 'reversed',
123
+ 'seamless',
124
+ 'selected',
125
+ 'webkitdirectory',
126
+ 'defer',
127
+ 'disablepictureinpicture',
128
+ 'disableremoteplayback',
129
+ ];
130
+
131
+ /**
132
+ * Returns true if name is a boolean DOM attribute
133
+ * @param {string} name
134
+ * @returns {boolean}
135
+ */
136
+ export function is_boolean_attribute(name) {
137
+ return DOM_BOOLEAN_ATTRIBUTES.includes(name);
138
+ }
139
+
140
+ const DOM_PROPERTIES = [
141
+ ...DOM_BOOLEAN_ATTRIBUTES,
142
+ 'formNoValidate',
143
+ 'isMap',
144
+ 'noModule',
145
+ 'playsInline',
146
+ 'readOnly',
147
+ 'value',
148
+ 'volume',
149
+ 'defaultValue',
150
+ 'defaultChecked',
151
+ 'srcObject',
152
+ 'noValidate',
153
+ 'allowFullscreen',
154
+ 'disablePictureInPicture',
155
+ 'disableRemotePlayback',
156
+ ];
157
+
158
+ /**
159
+ * Returns true if name is a DOM property
160
+ * @param {string} name
161
+ * @returns {boolean}
162
+ */
163
+ export function is_dom_property(name) {
164
+ return DOM_PROPERTIES.includes(name);
165
+ }
@@ -0,0 +1,3 @@
1
+ // Re-export acorn types with @tsrx/core augmentations applied.
2
+ import './parse';
3
+ export * from 'acorn';
@@ -0,0 +1,3 @@
1
+ // Re-export estree-jsx types with @tsrx/core augmentations applied.
2
+ import './index';
3
+ export * from 'estree-jsx';
@@ -0,0 +1,3 @@
1
+ // Re-export estree types with @tsrx/core augmentations applied.
2
+ import './index';
3
+ export * from 'estree';