@salve-software/react-native-nitro-jsdom 1.1.0 → 2.1.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.
Files changed (54) hide show
  1. package/android/CMakeLists.txt +6 -0
  2. package/cpp/HybridHtmlSandbox.cpp +14 -7
  3. package/cpp/HybridHtmlSandbox.hpp +1 -1
  4. package/cpp/lexbor/LexborDocument.cpp +65 -3
  5. package/cpp/lexbor/LexborDocument.hpp +17 -2
  6. package/cpp/quickjs/DOMBindings.cpp +14 -1
  7. package/cpp/quickjs/DOMBindingsInternal.cpp +67 -0
  8. package/cpp/quickjs/DOMBindingsInternal.hpp +39 -0
  9. package/cpp/quickjs/QuickJSRuntime.cpp +13 -0
  10. package/cpp/quickjs/QuickJSRuntime.hpp +29 -0
  11. package/cpp/quickjs/bindings/AbortBindings.cpp +14 -0
  12. package/cpp/quickjs/bindings/BlobBindings.cpp +28 -1
  13. package/cpp/quickjs/bindings/BlobBindings.hpp +3 -3
  14. package/cpp/quickjs/bindings/CSSOMBindings.cpp +96 -0
  15. package/cpp/quickjs/bindings/CSSOMBindings.hpp +3 -1
  16. package/cpp/quickjs/bindings/CustomElementsBindings.cpp +1 -1
  17. package/cpp/quickjs/bindings/DOMParserBindings.cpp +299 -0
  18. package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
  19. package/cpp/quickjs/bindings/DocumentBindings.cpp +174 -11
  20. package/cpp/quickjs/bindings/ElementBindings.cpp +516 -20
  21. package/cpp/quickjs/bindings/EventBindings.cpp +281 -5
  22. package/cpp/quickjs/bindings/EventBindings.hpp +3 -0
  23. package/cpp/quickjs/bindings/EventTargetBindings.cpp +75 -0
  24. package/cpp/quickjs/bindings/EventTargetBindings.hpp +24 -0
  25. package/cpp/quickjs/bindings/FetchBindings.cpp +43 -8
  26. package/cpp/quickjs/bindings/FormBindings.cpp +294 -0
  27. package/cpp/quickjs/bindings/FormBindings.hpp +9 -5
  28. package/cpp/quickjs/bindings/IntlBindings.cpp +352 -0
  29. package/cpp/quickjs/bindings/IntlBindings.hpp +29 -0
  30. package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
  31. package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
  32. package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +45 -0
  33. package/cpp/quickjs/bindings/TimerBindings.cpp +79 -0
  34. package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
  35. package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
  36. package/cpp/quickjs/bindings/UrlBindings.cpp +43 -0
  37. package/cpp/quickjs/bindings/WindowBindings.cpp +295 -4
  38. package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
  39. package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.cpp +241 -0
  40. package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.hpp +30 -0
  41. package/lib/commonjs/classes/JSDOM/JSDOM.class.js +2 -1
  42. package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
  43. package/lib/module/classes/JSDOM/JSDOM.class.js +2 -1
  44. package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
  45. package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
  46. package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts +5 -4
  47. package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts.map +1 -1
  48. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
  49. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
  50. package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
  51. package/package.json +17 -2
  52. package/src/classes/JSDOM/JSDOM.class.ts +2 -1
  53. package/src/classes/JSDOM/types/IJSDOMOptions.ts +5 -4
  54. package/src/specs/HtmlSandbox.nitro.ts +1 -1
@@ -125,6 +125,300 @@ const char* kFormBootstrapScript = R"JS(
125
125
  // Per spec, submit() bypasses the "submit" event and constraint validation.
126
126
  // There is no real navigation in this sandbox, so this is intentionally inert.
127
127
  };
128
+ Element.prototype.reset = function() {
129
+ if (!isFormElement(this)) return;
130
+ this.dispatchEvent(new Event('reset', { bubbles: true, cancelable: true }));
131
+ };
132
+
133
+ // ── select.options / .selectedIndex / .selectedOptions ────────────────────
134
+ function isSelectElement(el) {
135
+ return !!el && el.tagName === 'SELECT';
136
+ }
137
+
138
+ function selectOptionsArray(select) {
139
+ return Array.prototype.slice.call(select.querySelectorAll('option'));
140
+ }
141
+
142
+ Object.defineProperty(Element.prototype, 'options', {
143
+ configurable: true,
144
+ get: function() {
145
+ if (!isSelectElement(this)) return undefined;
146
+ var select = this;
147
+ var opts = selectOptionsArray(select);
148
+ opts.item = function(index) { return this[index] !== undefined ? this[index] : null; };
149
+ opts.namedItem = function(name) {
150
+ for (var i = 0; i < this.length; i++) {
151
+ if (this[i].id === name || this[i].getAttribute('name') === name) return this[i];
152
+ }
153
+ return null;
154
+ };
155
+ opts.add = function(option, before) {
156
+ var refNode = null;
157
+ if (typeof before === 'number') refNode = this[before] || null;
158
+ else if (before) refNode = before;
159
+ if (refNode) select.insertBefore(option, refNode);
160
+ else select.appendChild(option);
161
+ };
162
+ opts.remove = function(index) {
163
+ var target = this[index];
164
+ if (target) select.removeChild(target);
165
+ };
166
+ return opts;
167
+ },
168
+ });
169
+
170
+ Object.defineProperty(Element.prototype, 'selectedIndex', {
171
+ configurable: true,
172
+ get: function() {
173
+ if (!isSelectElement(this)) return -1;
174
+ var opts = selectOptionsArray(this);
175
+ for (var i = 0; i < opts.length; i++) {
176
+ if (opts[i].selected) return i;
177
+ }
178
+ return (!this.multiple && opts.length > 0) ? 0 : -1;
179
+ },
180
+ set: function(index) {
181
+ if (!isSelectElement(this)) return;
182
+ var opts = selectOptionsArray(this);
183
+ for (var i = 0; i < opts.length; i++) {
184
+ opts[i].selected = (i === index);
185
+ }
186
+ },
187
+ });
188
+
189
+ Object.defineProperty(Element.prototype, 'selectedOptions', {
190
+ configurable: true,
191
+ get: function() {
192
+ if (!isSelectElement(this)) return undefined;
193
+ var opts = selectOptionsArray(this);
194
+ var selected = opts.filter(function(o) { return o.selected; });
195
+ if (selected.length === 0 && !this.multiple && opts.length > 0) return [opts[0]];
196
+ return selected;
197
+ },
198
+ });
199
+
200
+ // ── Constraint Validation API (ValidityState, checkValidity, ...) ─────────
201
+ // Covers the subset real-world CMS forms actually hit: required, pattern,
202
+ // min/max/minlength/maxlength, type="email"/"url"/"number" and custom
203
+ // validity. `step` is intentionally not modeled (stepMismatch is always
204
+ // false) — it needs the same "default step base" arithmetic per input type
205
+ // that browsers hardcode, which isn't worth it for this sandbox's scope.
206
+ // reportValidity() has no UI to report against here, so it's just an alias
207
+ // for checkValidity() (same outcome jsdom has, for the same reason).
208
+
209
+ var VALIDITY_KEYS = [
210
+ 'valueMissing', 'typeMismatch', 'patternMismatch', 'tooLong', 'tooShort',
211
+ 'rangeUnderflow', 'rangeOverflow', 'stepMismatch', 'badInput', 'customError',
212
+ ];
213
+
214
+ function ValidityState(flags) {
215
+ this._flags = flags;
216
+ }
217
+ VALIDITY_KEYS.forEach(function(key) {
218
+ Object.defineProperty(ValidityState.prototype, key, {
219
+ enumerable: true,
220
+ get: function() { return !!this._flags[key]; },
221
+ });
222
+ });
223
+ Object.defineProperty(ValidityState.prototype, 'valid', {
224
+ enumerable: true,
225
+ get: function() {
226
+ var flags = this._flags;
227
+ return !VALIDITY_KEYS.some(function(key) { return flags[key]; });
228
+ },
229
+ });
230
+ globalThis.ValidityState = ValidityState;
231
+
232
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
233
+ var URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\/\S+$/;
234
+
235
+ function isFieldElement(el) {
236
+ return el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA';
237
+ }
238
+
239
+ function fieldType(el) {
240
+ if (el.tagName === 'TEXTAREA') return 'textarea';
241
+ if (el.tagName === 'SELECT') return 'select';
242
+ return (el.getAttribute('type') || 'text').toLowerCase();
243
+ }
244
+
245
+ function computeValidityFlags(el) {
246
+ var flags = {};
247
+ if (el._customValidity) flags.customError = true;
248
+
249
+ if (!isFieldElement(el) || el.hasAttribute('disabled')) return flags;
250
+
251
+ var type = fieldType(el);
252
+ if (type === 'hidden') return flags;
253
+
254
+ var required = el.hasAttribute('required');
255
+
256
+ if (type === 'checkbox' || type === 'radio') {
257
+ if (required && !el.checked) flags.valueMissing = true;
258
+ return flags;
259
+ }
260
+
261
+ var value = el.value;
262
+
263
+ if (el.tagName === 'SELECT') {
264
+ if (required && !value) flags.valueMissing = true;
265
+ return flags;
266
+ }
267
+
268
+ if (required && value === '') flags.valueMissing = true;
269
+ if (value === '') return flags;
270
+
271
+ if (type === 'email' && !EMAIL_RE.test(value)) flags.typeMismatch = true;
272
+ if (type === 'url' && !URL_RE.test(value)) flags.typeMismatch = true;
273
+
274
+ var pattern = el.getAttribute('pattern');
275
+ if (pattern) {
276
+ try {
277
+ if (!new RegExp('^(?:' + pattern + ')$').test(value)) flags.patternMismatch = true;
278
+ } catch (e) {
279
+ // Invalid pattern attribute: browsers treat this as never matching,
280
+ // but silently ignoring it is the safer default for a headless sandbox.
281
+ }
282
+ }
283
+
284
+ var maxlength = el.getAttribute('maxlength');
285
+ if (maxlength !== null && value.length > parseInt(maxlength, 10)) flags.tooLong = true;
286
+
287
+ var minlength = el.getAttribute('minlength');
288
+ if (minlength !== null && value.length < parseInt(minlength, 10)) flags.tooShort = true;
289
+
290
+ if (type === 'number' || type === 'range') {
291
+ var num = Number(value);
292
+ if (value.trim() === '' || isNaN(num)) {
293
+ flags.badInput = true;
294
+ } else {
295
+ var min = el.getAttribute('min');
296
+ var max = el.getAttribute('max');
297
+ if (min !== null && num < Number(min)) flags.rangeUnderflow = true;
298
+ if (max !== null && num > Number(max)) flags.rangeOverflow = true;
299
+ }
300
+ }
301
+
302
+ return flags;
303
+ }
304
+
305
+ Object.defineProperty(Element.prototype, 'validity', {
306
+ configurable: true,
307
+ get: function() { return new ValidityState(computeValidityFlags(this)); },
308
+ });
309
+
310
+ Object.defineProperty(Element.prototype, 'willValidate', {
311
+ configurable: true,
312
+ get: function() {
313
+ return isFieldElement(this) && !this.hasAttribute('disabled') && fieldType(this) !== 'hidden';
314
+ },
315
+ });
316
+
317
+ var DEFAULT_MESSAGES = {
318
+ valueMissing: 'Please fill out this field.',
319
+ typeMismatch: 'Please enter a valid value.',
320
+ patternMismatch: 'Please match the requested format.',
321
+ tooLong: 'Please shorten this text.',
322
+ tooShort: 'Please lengthen this text.',
323
+ rangeUnderflow: 'Value must be greater than or equal to the minimum.',
324
+ rangeOverflow: 'Value must be less than or equal to the maximum.',
325
+ badInput: 'Please enter a valid value.',
326
+ };
327
+
328
+ Object.defineProperty(Element.prototype, 'validationMessage', {
329
+ configurable: true,
330
+ get: function() {
331
+ if (this._customValidity) return this._customValidity;
332
+ var flags = computeValidityFlags(this);
333
+ for (var i = 0; i < VALIDITY_KEYS.length; i++) {
334
+ if (flags[VALIDITY_KEYS[i]]) return DEFAULT_MESSAGES[VALIDITY_KEYS[i]] || '';
335
+ }
336
+ return '';
337
+ },
338
+ });
339
+
340
+ Element.prototype.setCustomValidity = function(message) {
341
+ this._customValidity = message == null ? '' : String(message);
342
+ };
343
+
344
+ function fieldCheckValidity(el) {
345
+ var valid = new ValidityState(computeValidityFlags(el)).valid;
346
+ if (!valid) {
347
+ var evt = new Event('invalid', { bubbles: false, cancelable: true });
348
+ el.dispatchEvent(evt);
349
+ }
350
+ return valid;
351
+ }
352
+
353
+ function formFields(form) {
354
+ return Array.prototype.filter.call(form.querySelectorAll('input, select, textarea'), function(el) {
355
+ return !el.hasAttribute('disabled');
356
+ });
357
+ }
358
+
359
+ Element.prototype.checkValidity = function() {
360
+ if (this.tagName === 'FORM') {
361
+ var fields = formFields(this);
362
+ var allValid = true;
363
+ for (var i = 0; i < fields.length; i++) {
364
+ if (!fieldCheckValidity(fields[i])) allValid = false;
365
+ }
366
+ return allValid;
367
+ }
368
+ return fieldCheckValidity(this);
369
+ };
370
+
371
+ Element.prototype.reportValidity = function() {
372
+ return this.checkValidity();
373
+ };
374
+
375
+ // ── element.form / form.elements ────────────────────────────────────────
376
+ var FORM_ASSOCIATED_TAGS = ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON', 'FIELDSET', 'OUTPUT'];
377
+
378
+ function formControls(form) {
379
+ return Array.prototype.slice.call(form.querySelectorAll('input, select, textarea, button, fieldset, output'));
380
+ }
381
+
382
+ Object.defineProperty(Element.prototype, 'form', {
383
+ configurable: true,
384
+ get: function() {
385
+ if (FORM_ASSOCIATED_TAGS.indexOf(this.tagName) === -1) return undefined;
386
+ var formId = this.getAttribute('form');
387
+ if (formId) {
388
+ var byId = document.getElementById(formId);
389
+ if (byId && byId.tagName === 'FORM') return byId;
390
+ }
391
+ return this.closest('form');
392
+ },
393
+ });
394
+
395
+ Object.defineProperty(Element.prototype, 'elements', {
396
+ configurable: true,
397
+ get: function() {
398
+ if (this.tagName !== 'FORM') return undefined;
399
+ return formControls(this);
400
+ },
401
+ });
402
+
403
+ var LABELABLE_TAGS = ['BUTTON', 'INPUT', 'METER', 'OUTPUT', 'PROGRESS', 'SELECT', 'TEXTAREA'];
404
+
405
+ Object.defineProperty(Element.prototype, 'labels', {
406
+ configurable: true,
407
+ get: function() {
408
+ if (LABELABLE_TAGS.indexOf(this.tagName) === -1) return null;
409
+ if (this.tagName === 'INPUT' && (this.getAttribute('type') || '').toLowerCase() === 'hidden') return null;
410
+ var result = [];
411
+ if (this.id) {
412
+ var candidates = this.ownerDocument.querySelectorAll('label[for]');
413
+ for (var i = 0; i < candidates.length; i++) {
414
+ if (candidates[i].getAttribute('for') === this.id) result.push(candidates[i]);
415
+ }
416
+ }
417
+ var wrapping = this.closest('label');
418
+ if (wrapping && result.indexOf(wrapping) === -1) result.push(wrapping);
419
+ return result;
420
+ },
421
+ });
128
422
  })();
129
423
  )JS";
130
424
 
@@ -4,11 +4,15 @@
4
4
 
5
5
  namespace margelo::nitro::nitrojsdom {
6
6
 
7
- // Registers globalThis.FormData and Element.prototype.requestSubmit/submit
8
- // (tag-checked for "form" internally, mirroring ElementBindings' value/checked
9
- // pattern). Pure JS on top of the already-exposed querySelectorAll/Event
10
- // primitives — no native code needed. Must run after ElementBindings (for
11
- // globalThis.Element) and EventBindings (for globalThis.Event).
7
+ // Registers globalThis.FormData, Element.prototype.requestSubmit/submit, and
8
+ // the Constraint Validation API (globalThis.ValidityState,
9
+ // Element.prototype.validity/willValidate/validationMessage/checkValidity()/
10
+ // reportValidity()/setCustomValidity()). Tag-checked for "form"/"input"/
11
+ // "select"/"textarea" internally, mirroring ElementBindings' value/checked
12
+ // pattern. Pure JS on top of the already-exposed getAttribute/value/checked/
13
+ // querySelectorAll/Event primitives — no native code needed. Must run after
14
+ // ElementBindings (for globalThis.Element) and EventBindings (for
15
+ // globalThis.Event).
12
16
  struct FormBindings {
13
17
  static void install(JSContext* ctx);
14
18
  };
@@ -0,0 +1,352 @@
1
+ #include "IntlBindings.hpp"
2
+ #include <cstring>
3
+
4
+ namespace margelo::nitro::nitrojsdom {
5
+
6
+ namespace {
7
+
8
+ const char* kIntlBootstrapScript = R"JS(
9
+ (function() {
10
+ var LOCALE_DATA = {
11
+ en: {
12
+ months: {
13
+ long: ['January','February','March','April','May','June','July','August','September','October','November','December'],
14
+ short: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
15
+ narrow: ['J','F','M','A','M','J','J','A','S','O','N','D'],
16
+ },
17
+ weekdays: {
18
+ long: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
19
+ short: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
20
+ narrow: ['S','M','T','W','T','F','S'],
21
+ },
22
+ dayPeriod: ['AM', 'PM'],
23
+ decimal: '.',
24
+ group: ',',
25
+ dateOrder: 'MDY',
26
+ currencySpace: false,
27
+ },
28
+ pt: {
29
+ months: {
30
+ long: ['janeiro','fevereiro','março','abril','maio','junho','julho','agosto','setembro','outubro','novembro','dezembro'],
31
+ short: ['jan','fev','mar','abr','mai','jun','jul','ago','set','out','nov','dez'],
32
+ narrow: ['J','F','M','A','M','J','J','A','S','O','N','D'],
33
+ },
34
+ weekdays: {
35
+ long: ['domingo','segunda-feira','terça-feira','quarta-feira','quinta-feira','sexta-feira','sábado'],
36
+ short: ['dom','seg','ter','qua','qui','sex','sáb'],
37
+ narrow: ['D','S','T','Q','Q','S','S'],
38
+ },
39
+ dayPeriod: ['AM', 'PM'],
40
+ decimal: ',',
41
+ group: '.',
42
+ dateOrder: 'DMY',
43
+ currencySpace: true,
44
+ },
45
+ };
46
+
47
+ var CURRENCY_SYMBOLS = {
48
+ USD: '$', EUR: '€', GBP: '£', JPY: '¥', BRL: 'R$', CAD: 'CA$', AUD: 'A$', CHF: 'CHF', CNY: 'CN¥', INR: '₹', MXN: 'MX$',
49
+ };
50
+
51
+ var CURRENCY_DECIMALS = {
52
+ JPY: 0, KRW: 0, VND: 0, CLP: 0, ISK: 0, UGX: 0, XAF: 0, XOF: 0, XPF: 0,
53
+ BHD: 3, IQD: 3, JOD: 3, KWD: 3, OMR: 3, TND: 3,
54
+ };
55
+
56
+ function currencyDecimals(code) {
57
+ return CURRENCY_DECIMALS[code] !== undefined ? CURRENCY_DECIMALS[code] : 2;
58
+ }
59
+
60
+ function localeLanguage(locales) {
61
+ var tag = Array.isArray(locales) ? locales[0] : locales;
62
+ tag = String(tag || 'en');
63
+ return tag.split(/[-_]/)[0].toLowerCase();
64
+ }
65
+
66
+ function resolveLocale(locales) {
67
+ var lang = localeLanguage(locales);
68
+ return LOCALE_DATA[lang] || LOCALE_DATA.en;
69
+ }
70
+
71
+ function groupInteger(digits, groupSep) {
72
+ var out = '';
73
+ var count = 0;
74
+ for (var i = digits.length - 1; i >= 0; i--) {
75
+ out = digits.charAt(i) + out;
76
+ count++;
77
+ if (count % 3 === 0 && i !== 0) out = groupSep + out;
78
+ }
79
+ return out;
80
+ }
81
+
82
+ function formatFixed(value, minFrac, maxFrac, useGrouping, data) {
83
+ var negative = value < 0;
84
+ var abs = Math.abs(value);
85
+ var fixed = abs.toFixed(maxFrac);
86
+ var parts = fixed.split('.');
87
+ var intPart = parts[0];
88
+ var fracPart = parts[1] || '';
89
+
90
+ while (fracPart.length > minFrac && fracPart.charAt(fracPart.length - 1) === '0') {
91
+ fracPart = fracPart.slice(0, -1);
92
+ }
93
+
94
+ var out = useGrouping ? groupInteger(intPart, data.group) : intPart;
95
+ if (fracPart.length > 0) out += data.decimal + fracPart;
96
+ return (negative ? '-' : '') + out;
97
+ }
98
+
99
+ function NumberFormat(locales, options) {
100
+ this._data = resolveLocale(locales);
101
+ options = options || {};
102
+ this._style = options.style || 'decimal';
103
+ if (this._style === 'currency') {
104
+ if (!options.currency) throw new TypeError('Currency code is required with currency style.');
105
+ this._currency = options.currency;
106
+ }
107
+ var currDecimals = this._style === 'currency' ? currencyDecimals(this._currency) : undefined;
108
+ var defaultMinFrac = this._style === 'currency' ? currDecimals : 0;
109
+ var defaultMaxFrac = this._style === 'currency' ? currDecimals : (this._style === 'percent' ? 0 : 3);
110
+ this._minFrac = options.minimumFractionDigits !== undefined ? options.minimumFractionDigits : defaultMinFrac;
111
+ this._maxFrac = options.maximumFractionDigits !== undefined ? options.maximumFractionDigits : Math.max(defaultMaxFrac, this._minFrac);
112
+ this._useGrouping = options.useGrouping !== undefined ? !!options.useGrouping : true;
113
+ }
114
+
115
+ NumberFormat.prototype.format = function(value) {
116
+ value = Number(value);
117
+ if (this._style === 'percent') value = value * 100;
118
+ var formatted = formatFixed(value, this._minFrac, this._maxFrac, this._useGrouping, this._data);
119
+ if (this._style === 'percent') return formatted + '%';
120
+ if (this._style === 'currency') {
121
+ var symbol = CURRENCY_SYMBOLS[this._currency] || this._currency;
122
+ return symbol + (this._data.currencySpace ? ' ' : '') + formatted;
123
+ }
124
+ return formatted;
125
+ };
126
+
127
+ NumberFormat.prototype.resolvedOptions = function() {
128
+ return {
129
+ style: this._style,
130
+ currency: this._style === 'currency' ? this._currency : undefined,
131
+ minimumFractionDigits: this._minFrac,
132
+ maximumFractionDigits: this._maxFrac,
133
+ useGrouping: this._useGrouping,
134
+ };
135
+ };
136
+
137
+ function pad2(n) { return (n < 10 ? '0' : '') + n; }
138
+
139
+ var DATE_STYLE_OPTIONS = {
140
+ short: { year: '2-digit', month: 'numeric', day: 'numeric' },
141
+ medium: { year: 'numeric', month: 'short', day: 'numeric' },
142
+ long: { year: 'numeric', month: 'long', day: 'numeric' },
143
+ full: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' },
144
+ };
145
+ var TIME_STYLE_OPTIONS = {
146
+ short: { hour: 'numeric', minute: 'numeric' },
147
+ medium: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
148
+ long: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
149
+ full: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
150
+ };
151
+
152
+ function DateTimeFormat(locales, options) {
153
+ this._data = resolveLocale(locales);
154
+ options = options || {};
155
+
156
+ if (options.dateStyle || options.timeStyle) {
157
+ var withDate = options.dateStyle ? (DATE_STYLE_OPTIONS[options.dateStyle] || DATE_STYLE_OPTIONS.medium) : {};
158
+ var withTime = options.timeStyle ? (TIME_STYLE_OPTIONS[options.timeStyle] || TIME_STYLE_OPTIONS.short) : {};
159
+ options = Object.assign({}, withDate, withTime, options);
160
+ } else if (Object.keys(options).length === 0) {
161
+ options = { year: 'numeric', month: 'numeric', day: 'numeric' };
162
+ }
163
+
164
+ this._options = options;
165
+ }
166
+
167
+ function datePartFor(date, o, data) {
168
+ var textual = o.month === 'long' || o.month === 'short' || o.month === 'narrow';
169
+ var day = o.day ? (o.day === '2-digit' ? pad2(date.getDate()) : String(date.getDate())) : '';
170
+ var year = o.year ? (o.year === '2-digit' ? pad2(date.getFullYear() % 100) : String(date.getFullYear())) : '';
171
+ var month = o.month
172
+ ? (textual ? data.months[o.month][date.getMonth()] : (o.month === '2-digit' ? pad2(date.getMonth() + 1) : String(date.getMonth() + 1)))
173
+ : '';
174
+
175
+ if (!month && !day && !year) return '';
176
+
177
+ if (textual) {
178
+ if (data.dateOrder === 'MDY') {
179
+ var head = day ? month + ' ' + day : month;
180
+ return year ? head + ', ' + year : head;
181
+ }
182
+ return [day, month, year].filter(Boolean).join(' ');
183
+ }
184
+
185
+ var ordered = data.dateOrder === 'MDY' ? [month, day, year] : [day, month, year];
186
+ return ordered.filter(Boolean).join('/');
187
+ }
188
+
189
+ DateTimeFormat.prototype.format = function(date) {
190
+ date = date === undefined ? new Date() : (date instanceof Date ? date : new Date(date));
191
+ var o = this._options;
192
+ var data = this._data;
193
+ var pieces = [];
194
+
195
+ var datePart = datePartFor(date, o, data);
196
+ if (o.weekday) {
197
+ var w = date.getDay();
198
+ var weekdayName = data.weekdays[o.weekday === 'narrow' ? 'narrow' : (o.weekday === 'long' ? 'long' : 'short')][w];
199
+ pieces.push(datePart ? weekdayName + ', ' + datePart : weekdayName);
200
+ } else if (datePart) {
201
+ pieces.push(datePart);
202
+ }
203
+
204
+ if (o.hour) {
205
+ var h = date.getHours();
206
+ var hour12 = o.hour12 !== false;
207
+ var hourVal = hour12 ? (h % 12 === 0 ? 12 : h % 12) : h;
208
+ var timeSegs = [o.hour === '2-digit' ? pad2(hourVal) : String(hourVal)];
209
+ if (o.minute) timeSegs.push(pad2(date.getMinutes()));
210
+ if (o.second) timeSegs.push(pad2(date.getSeconds()));
211
+ var timeStr = timeSegs.join(':');
212
+ if (hour12) timeStr += ' ' + (h < 12 ? data.dayPeriod[0] : data.dayPeriod[1]);
213
+ pieces.push(timeStr);
214
+ } else if (o.minute) {
215
+ var timeSegs2 = [pad2(date.getMinutes())];
216
+ if (o.second) timeSegs2.push(pad2(date.getSeconds()));
217
+ pieces.push(timeSegs2.join(':'));
218
+ }
219
+
220
+ return pieces.join(', ');
221
+ };
222
+
223
+ DateTimeFormat.prototype.resolvedOptions = function() {
224
+ return Object.assign({}, this._options);
225
+ };
226
+
227
+ function pluralCategoryFor(n, lang) {
228
+ n = Math.abs(Number(n));
229
+ if (lang === 'pt') return (n === 0 || n === 1) ? 'one' : 'other';
230
+ return n === 1 ? 'one' : 'other';
231
+ }
232
+
233
+ function PluralRules(locales, options) {
234
+ this._lang = localeLanguage(locales);
235
+ options = options || {};
236
+ this._type = options.type || 'cardinal';
237
+ }
238
+
239
+ PluralRules.prototype.select = function(n) {
240
+ return pluralCategoryFor(n, this._lang);
241
+ };
242
+
243
+ PluralRules.prototype.resolvedOptions = function() {
244
+ return { locale: this._lang, type: this._type, pluralCategories: ['one', 'other'] };
245
+ };
246
+
247
+ var RTF_UNITS = {
248
+ en: {
249
+ year: ['year', 'years'], quarter: ['quarter', 'quarters'], month: ['month', 'months'],
250
+ week: ['week', 'weeks'], day: ['day', 'days'], hour: ['hour', 'hours'],
251
+ minute: ['minute', 'minutes'], second: ['second', 'seconds'],
252
+ },
253
+ pt: {
254
+ year: ['ano', 'anos'], quarter: ['trimestre', 'trimestres'], month: ['mês', 'meses'],
255
+ week: ['semana', 'semanas'], day: ['dia', 'dias'], hour: ['hora', 'horas'],
256
+ minute: ['minuto', 'minutos'], second: ['segundo', 'segundos'],
257
+ },
258
+ };
259
+
260
+ var RTF_PATTERNS = {
261
+ en: { past: '{0} {1} ago', future: 'in {0} {1}' },
262
+ pt: { past: 'há {0} {1}', future: 'em {0} {1}' },
263
+ };
264
+
265
+ var RTF_AUTO = {
266
+ en: {
267
+ day: { '-1': 'yesterday', '0': 'today', '1': 'tomorrow' },
268
+ week: { '-1': 'last week', '0': 'this week', '1': 'next week' },
269
+ month: { '-1': 'last month', '0': 'this month', '1': 'next month' },
270
+ year: { '-1': 'last year', '0': 'this year', '1': 'next year' },
271
+ second: { '0': 'now' },
272
+ },
273
+ pt: {
274
+ day: { '-1': 'ontem', '0': 'hoje', '1': 'amanhã' },
275
+ week: { '-1': 'semana passada', '0': 'esta semana', '1': 'semana que vem' },
276
+ month: { '-1': 'mês passado', '0': 'este mês', '1': 'próximo mês' },
277
+ year: { '-1': 'ano passado', '0': 'este ano', '1': 'próximo ano' },
278
+ second: { '0': 'agora' },
279
+ },
280
+ };
281
+
282
+ var RTF_UNIT_ALIASES = {
283
+ years: 'year', quarters: 'quarter', months: 'month', weeks: 'week',
284
+ days: 'day', hours: 'hour', minutes: 'minute', seconds: 'second',
285
+ };
286
+
287
+ function RelativeTimeFormat(locales, options) {
288
+ this._lang = RTF_UNITS[localeLanguage(locales)] ? localeLanguage(locales) : 'en';
289
+ options = options || {};
290
+ this._numeric = options.numeric || 'always';
291
+ this._style = options.style || 'long';
292
+ }
293
+
294
+ RelativeTimeFormat.prototype.format = function(value, unit) {
295
+ value = Number(value);
296
+ unit = RTF_UNIT_ALIASES[unit] || unit;
297
+ var units = RTF_UNITS[this._lang][unit];
298
+ if (!units) throw new RangeError('Invalid unit argument for RelativeTimeFormat.format()');
299
+
300
+ if (this._numeric === 'auto') {
301
+ var autoData = RTF_AUTO[this._lang][unit];
302
+ if (autoData && autoData[String(value)] !== undefined) {
303
+ return autoData[String(value)];
304
+ }
305
+ }
306
+
307
+ var n = Math.abs(value);
308
+ var unitName = units[pluralCategoryFor(n, this._lang) === 'one' ? 0 : 1];
309
+ var pattern = value < 0 ? RTF_PATTERNS[this._lang].past : RTF_PATTERNS[this._lang].future;
310
+ return pattern.replace('{0}', String(n)).replace('{1}', unitName);
311
+ };
312
+
313
+ RelativeTimeFormat.prototype.resolvedOptions = function() {
314
+ return { locale: this._lang, style: this._style, numeric: this._numeric };
315
+ };
316
+
317
+ globalThis.Intl = globalThis.Intl || {};
318
+ globalThis.Intl.NumberFormat = NumberFormat;
319
+ globalThis.Intl.DateTimeFormat = DateTimeFormat;
320
+ globalThis.Intl.PluralRules = PluralRules;
321
+ globalThis.Intl.RelativeTimeFormat = RelativeTimeFormat;
322
+
323
+ function hasOwnKeys(o) { return !!o && Object.keys(o).length > 0; }
324
+
325
+ Number.prototype.toLocaleString = function(locales, options) {
326
+ return new NumberFormat(locales, options).format(this);
327
+ };
328
+
329
+ Date.prototype.toLocaleString = function(locales, options) {
330
+ return new DateTimeFormat(locales, hasOwnKeys(options) ? options : { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' }).format(this);
331
+ };
332
+ Date.prototype.toLocaleDateString = function(locales, options) {
333
+ return new DateTimeFormat(locales, hasOwnKeys(options) ? options : { year: 'numeric', month: 'numeric', day: 'numeric' }).format(this);
334
+ };
335
+ Date.prototype.toLocaleTimeString = function(locales, options) {
336
+ return new DateTimeFormat(locales, hasOwnKeys(options) ? options : { hour: 'numeric', minute: 'numeric', second: 'numeric' }).format(this);
337
+ };
338
+ })();
339
+ )JS";
340
+
341
+ } // namespace
342
+
343
+ void IntlBindings::install(JSContext* ctx) {
344
+ JSValue result = JS_Eval(ctx, kIntlBootstrapScript, strlen(kIntlBootstrapScript),
345
+ "<intl-bootstrap>", JS_EVAL_TYPE_GLOBAL);
346
+ if (JS_IsException(result)) {
347
+ JS_FreeValue(ctx, JS_GetException(ctx));
348
+ }
349
+ JS_FreeValue(ctx, result);
350
+ }
351
+
352
+ } // namespace margelo::nitro::nitrojsdom