Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
## [2.0.0](https://github.com/Salve-Software/react-native-nitro-jsdom/compare/v1.1.0...v2.0.0) (2026-07-24)

### ⚠ BREAKING CHANGES

* **release:** commits were mapped to a minor release, same as a
plain feat, so no commit could ever trigger a major bump. Regular
feat/fix commits are unaffected — only commits marked with ! or a
* **release:** footer are impacted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

### ✨ Features

* **blob:** add File constructor ([6a52021](https://github.com/Salve-Software/react-native-nitro-jsdom/commit/6a5202123fd60262bc7120ff4eca2d754a09c941))
Expand Down
1 change: 1 addition & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ add_library(${PACKAGE_NAME} SHARED
../cpp/quickjs/bindings/LayoutStubBindings.cpp
../cpp/quickjs/bindings/TreeWalkerBindings.cpp
../cpp/quickjs/bindings/EventTargetBindings.cpp
../cpp/quickjs/bindings/WindowNamedPropertiesBindings.cpp
${QUICKJS_SOURCES}
)

Expand Down
3 changes: 3 additions & 0 deletions cpp/quickjs/DOMBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "bindings/LayoutStubBindings.hpp"
#include "bindings/TreeWalkerBindings.hpp"
#include "bindings/EventTargetBindings.hpp"
#include "bindings/WindowNamedPropertiesBindings.hpp"
#include <lexbor/html/html.h>
#include <lexbor/dom/dom.h>

Expand Down Expand Up @@ -90,6 +91,8 @@ void DOMBindings::install(QuickJSRuntime* runtime, LexborDocument* document) {
rctx->mutation_observers->install(ctx, doc_node);
}
}

WindowNamedPropertiesBindings::install(ctx); // must run last, after localStorage/sessionStorage exist
}

} // namespace margelo::nitro::nitrojsdom
92 changes: 92 additions & 0 deletions cpp/quickjs/bindings/IntlBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,101 @@ const char* kIntlBootstrapScript = R"JS(
return Object.assign({}, this._options);
};

function pluralCategoryFor(n, lang) {
n = Math.abs(Number(n));
if (lang === 'pt') return (n === 0 || n === 1) ? 'one' : 'other';
return n === 1 ? 'one' : 'other';
}

function PluralRules(locales, options) {
this._lang = localeLanguage(locales);
options = options || {};
this._type = options.type || 'cardinal';
}

PluralRules.prototype.select = function(n) {
return pluralCategoryFor(n, this._lang);
};

PluralRules.prototype.resolvedOptions = function() {
return { locale: this._lang, type: this._type, pluralCategories: ['one', 'other'] };
};

var RTF_UNITS = {
en: {
year: ['year', 'years'], quarter: ['quarter', 'quarters'], month: ['month', 'months'],
week: ['week', 'weeks'], day: ['day', 'days'], hour: ['hour', 'hours'],
minute: ['minute', 'minutes'], second: ['second', 'seconds'],
},
pt: {
year: ['ano', 'anos'], quarter: ['trimestre', 'trimestres'], month: ['mês', 'meses'],
week: ['semana', 'semanas'], day: ['dia', 'dias'], hour: ['hora', 'horas'],
minute: ['minuto', 'minutos'], second: ['segundo', 'segundos'],
},
};

var RTF_PATTERNS = {
en: { past: '{0} {1} ago', future: 'in {0} {1}' },
pt: { past: 'há {0} {1}', future: 'em {0} {1}' },
};

var RTF_AUTO = {
en: {
day: { '-1': 'yesterday', '0': 'today', '1': 'tomorrow' },
week: { '-1': 'last week', '0': 'this week', '1': 'next week' },
month: { '-1': 'last month', '0': 'this month', '1': 'next month' },
year: { '-1': 'last year', '0': 'this year', '1': 'next year' },
second: { '0': 'now' },
},
pt: {
day: { '-1': 'ontem', '0': 'hoje', '1': 'amanhã' },
week: { '-1': 'semana passada', '0': 'esta semana', '1': 'semana que vem' },
month: { '-1': 'mês passado', '0': 'este mês', '1': 'próximo mês' },
year: { '-1': 'ano passado', '0': 'este ano', '1': 'próximo ano' },
second: { '0': 'agora' },
},
};

var RTF_UNIT_ALIASES = {
years: 'year', quarters: 'quarter', months: 'month', weeks: 'week',
days: 'day', hours: 'hour', minutes: 'minute', seconds: 'second',
};

function RelativeTimeFormat(locales, options) {
this._lang = RTF_UNITS[localeLanguage(locales)] ? localeLanguage(locales) : 'en';
options = options || {};
this._numeric = options.numeric || 'always';
this._style = options.style || 'long';
}

RelativeTimeFormat.prototype.format = function(value, unit) {
value = Number(value);
unit = RTF_UNIT_ALIASES[unit] || unit;
var units = RTF_UNITS[this._lang][unit];
if (!units) throw new RangeError('Invalid unit argument for RelativeTimeFormat.format()');

if (this._numeric === 'auto') {
var autoData = RTF_AUTO[this._lang][unit];
if (autoData && autoData[String(value)] !== undefined) {
return autoData[String(value)];
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

var n = Math.abs(value);
var unitName = units[pluralCategoryFor(n, this._lang) === 'one' ? 0 : 1];
var pattern = value < 0 ? RTF_PATTERNS[this._lang].past : RTF_PATTERNS[this._lang].future;
return pattern.replace('{0}', String(n)).replace('{1}', unitName);
};

RelativeTimeFormat.prototype.resolvedOptions = function() {
return { locale: this._lang, style: this._style, numeric: this._numeric };
};

globalThis.Intl = globalThis.Intl || {};
globalThis.Intl.NumberFormat = NumberFormat;
globalThis.Intl.DateTimeFormat = DateTimeFormat;
globalThis.Intl.PluralRules = PluralRules;
globalThis.Intl.RelativeTimeFormat = RelativeTimeFormat;

function hasOwnKeys(o) { return !!o && Object.keys(o).length > 0; }

Expand Down
24 changes: 18 additions & 6 deletions cpp/quickjs/bindings/IntlBindings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@

namespace margelo::nitro::nitrojsdom {

// Registers a pure-JS globalThis.Intl.NumberFormat/DateTimeFormat (no ICU —
// QuickJS ships none). Locale data is a small hand-built table (en + pt, the
// two this project's users actually need), not real CLDR data. Also rewires
// Number.prototype.toLocaleString and Date.prototype.toLocaleString/
// toLocaleDateString/toLocaleTimeString to go through these instead of
// QuickJS's own locale-blind built-ins.
// Registers a pure-JS globalThis.Intl.NumberFormat/DateTimeFormat/
// PluralRules/RelativeTimeFormat (no ICU — QuickJS ships none). Locale data
// is a small hand-built table (en + pt, the two this project's users
// actually need), not real CLDR data. Also rewires Number.prototype.
// toLocaleString and Date.prototype.toLocaleString/toLocaleDateString/
// toLocaleTimeString to go through these instead of QuickJS's own
// locale-blind built-ins.
//
// RelativeTimeFormat's `style` option ('long'/'short'/'narrow') is accepted
// but 'short'/'narrow' render identically to 'long' — there's no abbreviated
// unit-name table, same trade-off DateTimeFormat already made collapsing
// timeStyle 'long'/'full'. `numeric: 'auto'` only has special phrasing
// ("yesterday", "next week", ..., plus "now"/"agora" for second: 0) for
// day/week/month/year/second — hour/minute/quarter always render
// numerically, matching real Intl's own behavior for units without a
// natural auto phrase. Auto phrasing only ever applies to exact supported
// integer values (e.g. -1/0/1) — a fractional value like 1.2 always falls
// through to numeric formatting rather than rounding into a phrase.
struct IntlBindings {
static void install(JSContext* ctx);
};
Expand Down
Loading
Loading