anu-verzum 2.0.0 → 2.2.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h3>@author: <strong>Anubis-programmer</strong></h3>
6
6
  <h3>@license: <strong>MIT</strong></h3>
7
- <h3>@version: <strong>2.0.0</strong></h3>
7
+ <h3>@version: <strong>2.2.0</strong></h3>
8
8
 
9
9
  <br>
10
10
 
@@ -88,6 +88,17 @@ module.exports = require('anu-verzum/webpack.config')(__dirname, {
88
88
  });
89
89
  ```
90
90
 
91
+ Use `rules` to append additional webpack module rules after the built-in `babel-loader` rule. This is how you wire up CSS, LESS, images, or any other asset type:
92
+
93
+ ```js
94
+ module.exports = require('anu-verzum/webpack.config')(__dirname, {
95
+ rules: [
96
+ { test: /\.css$/, use: ['style-loader', 'css-loader'] },
97
+ { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }
98
+ ]
99
+ });
100
+ ```
101
+
91
102
  <h3 id="importing-in-your-files">Importing in your files</h3>
92
103
 
93
104
  Every file that contains JSX must import `Anu`, because the JSX transform expands to `Anu.createElement(...)` calls at compile time:
@@ -185,6 +196,8 @@ The following types are exported from `anu-verzum` for use in consumer projects:
185
196
  | `CreateSelectorFn` | Overloaded interface for `Anu.store.createSelector` — enables full type inference on transformation parameters |
186
197
  | `ApiSuccessResponse<T>` | Successful HTTP response `{ status: number; response: T \| null }` |
187
198
  | `ApiErrorResponse` | Error HTTP response `{ status: number; response: null }` |
199
+ | `FormatNumberOptions` | Options for `Anu.Intl.formatNumber` — `Intl.NumberFormatOptions` plus an optional `locale` |
200
+ | `ParseNumberOptions` | Options for `Anu.Intl.parseNumber` — `{ locale?: string }` |
188
201
 
189
202
  #### Library development scripts
190
203
 
@@ -14,8 +14,16 @@ export interface AbbreviateNumberOptions {
14
14
  decimalPlaces?: number;
15
15
  decimalSign?: string;
16
16
  }
17
+ export interface FormatNumberOptions extends Intl.NumberFormatOptions {
18
+ locale?: string;
19
+ }
20
+ export interface ParseNumberOptions {
21
+ locale?: string;
22
+ }
17
23
  declare const Intl: {
18
24
  abbreviateNumber: (value: number, options?: AbbreviateNumberOptions) => string | number;
25
+ formatNumber: (value: number, options?: FormatNumberOptions) => string;
26
+ parseNumber: (text: string, options?: ParseNumberOptions) => number | null;
19
27
  FormattedMessage: ({ id, values, defaultMessage }: FormattedMessageProps) => AnuElement;
20
28
  formatMessage: (id: string, values?: Record<string, string | number>, defaultMessage?: string) => string;
21
29
  Provider: ({ locale, messages, defaultLocale, children }: IntlProviderProps) => AnuElement | null;
@@ -11,6 +11,11 @@ let __messagesContext = {
11
11
  locale: undefined,
12
12
  messages: {}
13
13
  };
14
+
15
+ // `Intl.NumberFormatOptions` here refers to the global ECMAScript Intl namespace
16
+ // (type space), not the local `Intl` API object declared at the bottom of this
17
+ // file — a `const` introduces only a value binding, so the type reference is safe.
18
+
14
19
  const IntlProvider = ({
15
20
  locale,
16
21
  messages,
@@ -113,8 +118,15 @@ const formatMessage = (id, values, defaultMessage) => {
113
118
  }
114
119
  return textValue;
115
120
  };
121
+
122
+ // Reduce any BCP 47 tag to its lowercase language subtag, e.g. 'hu-HU' / 'HU' → 'hu'.
123
+ // Used for the exact dictionary-key lookups below (UNITS / DECIMAL_SIGN), which key
124
+ // on the language only — so a Provider locale of 'hu', 'HU', or 'hu-HU' all resolve
125
+ // to the same Hungarian entry. (formatNumber/parseNumber keep the full tag, since the
126
+ // region subtag is meaningful to Intl.NumberFormat — e.g. en-US vs en-GB vs en-IN.)
127
+ const getLanguageSubtag = locale => locale ? locale.toLowerCase().split('-')[0] : undefined;
116
128
  const abbreviateNumber = (value, options = {}) => {
117
- const getByLocale = values => values[__messagesContext.locale || 'default'] || values['default'];
129
+ const getByLocale = values => values[getLanguageSubtag(__messagesContext.locale) || 'default'] || values['default'];
118
130
  const UNITS = {
119
131
  hu: ['E', 'm', 'M', 'b'],
120
132
  default: ['K', 'M', 'B', 'T']
@@ -162,8 +174,70 @@ const abbreviateNumber = (value, options = {}) => {
162
174
  }
163
175
  return value;
164
176
  };
177
+
178
+ // The local `Intl` object below shadows the global ECMAScript `Intl`, so number
179
+ // formatting/parsing must reach the real `Intl.NumberFormat` via `globalThis.Intl`.
180
+ const resolveLocale = locale => locale || __messagesContext.locale || undefined;
181
+ const formatNumber = (value, options = {}) => {
182
+ const {
183
+ locale,
184
+ ...numberFormatOptions
185
+ } = options;
186
+ if (typeof value !== 'number' || isNaN(value)) {
187
+ return String(value);
188
+ }
189
+ try {
190
+ return new globalThis.Intl.NumberFormat(resolveLocale(locale), numberFormatOptions).format(value);
191
+ } catch (err) {
192
+ console.error(err);
193
+ return String(value);
194
+ }
195
+ };
196
+ const parseNumber = (text, options = {}) => {
197
+ if (typeof text !== 'string') {
198
+ return null;
199
+ }
200
+ try {
201
+ // Discover the locale's grouping/decimal/minus marks from a known sample,
202
+ // then normalize the input back into a plain JS-parseable number string.
203
+ const parts = new globalThis.Intl.NumberFormat(resolveLocale(options.locale)).formatToParts(-12345.6);
204
+ const groupSign = parts.find(p => p.type === 'group')?.value ?? '';
205
+ const decimalSign = parts.find(p => p.type === 'decimal')?.value ?? '.';
206
+ const minusSign = parts.find(p => p.type === 'minusSign')?.value ?? '-';
207
+ let normalized = text.trim();
208
+ const isNegative = normalized.indexOf('-') > -1 || !!minusSign && normalized.indexOf(minusSign) > -1;
209
+
210
+ // Drop grouping separators: the explicit locale group sign plus any
211
+ // whitespace (JS `\s` covers NBSP/NNBSP, which several locales group with).
212
+ if (groupSign) {
213
+ normalized = normalized.split(groupSign).join('');
214
+ }
215
+ normalized = normalized.replace(/\s/g, '');
216
+
217
+ // Convert the locale decimal sign to a dot.
218
+ if (decimalSign && decimalSign !== '.') {
219
+ normalized = normalized.split(decimalSign).join('.');
220
+ }
221
+
222
+ // Keep only digits and the decimal dot; sign is reapplied from isNegative.
223
+ normalized = normalized.replace(/[^0-9.]/g, '');
224
+ if (normalized === '' || normalized === '.') {
225
+ return null;
226
+ }
227
+ const result = Number(normalized);
228
+ if (isNaN(result)) {
229
+ return null;
230
+ }
231
+ return isNegative ? -result : result;
232
+ } catch (err) {
233
+ console.error(err);
234
+ return null;
235
+ }
236
+ };
165
237
  const Intl = {
166
238
  abbreviateNumber,
239
+ formatNumber,
240
+ parseNumber,
167
241
  FormattedMessage,
168
242
  formatMessage,
169
243
  Provider: IntlProvider
package/dist/index.d.ts CHANGED
@@ -102,6 +102,8 @@ declare const Anu: {
102
102
  };
103
103
  Intl: {
104
104
  abbreviateNumber: (value: number, options?: import(".").AbbreviateNumberOptions) => string | number;
105
+ formatNumber: (value: number, options?: import(".").FormatNumberOptions) => string;
106
+ parseNumber: (text: string, options?: import(".").ParseNumberOptions) => number | null;
105
107
  FormattedMessage: ({ id, values, defaultMessage }: import("./core/components/Intl").FormattedMessageProps) => AnuElement;
106
108
  formatMessage: (id: string, values?: Record<string, string | number>, defaultMessage?: string) => string;
107
109
  Provider: ({ locale, messages, defaultLocale, children }: import("./core/components/Intl").IntlProviderProps) => AnuElement | null;
@@ -160,7 +162,7 @@ export type { ApiSuccessResponse, ApiErrorResponse } from './server-api/server-a
160
162
  export { Component, Fragment, createElement, createRef, createPortal, createContext, render, goTo };
161
163
  export { AnulyticsProvider, trackEvent };
162
164
  export { store, ServerAPI, Utils, Connector, Feature, History, Intl };
163
- export type { AbbreviateNumberOptions } from './core/components/Intl';
165
+ export type { AbbreviateNumberOptions, FormatNumberOptions, ParseNumberOptions } from './core/components/Intl';
164
166
  declare global {
165
167
  namespace JSX {
166
168
  interface IntrinsicAttributes {
@@ -15,8 +15,12 @@ const queryAllByLabelText = (container, label) => {
15
15
  }
16
16
  const forAttr = labelEl.getAttribute('for');
17
17
  if (forAttr) {
18
- const input = container.querySelector(`#${CSS.escape(forAttr)}`);
19
- if (input) {
18
+ // The `for` attribute is already an element id, so resolve it with
19
+ // getElementById — no CSS.escape needed (jsdom omits the browser-only
20
+ // `CSS` global), and it handles any id characters. Scope to container.
21
+ const doc = container.ownerDocument ?? document;
22
+ const input = doc.getElementById(forAttr);
23
+ if (input && container.contains(input)) {
20
24
  results.push(input);
21
25
  }
22
26
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anu-verzum",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "A \"React-like\" UI library that supports JSX syntax, Redux-like state management, array-rendering, i18n, routing and many more.",
5
5
  "keywords": [
6
6
  "anu-verzum",
package/webpack.config.js CHANGED
@@ -17,7 +17,8 @@ module.exports = (projectRoot, options = {}) => ({
17
17
  test: /\.[jt]sx?$/,
18
18
  exclude: /node_modules/,
19
19
  use: 'babel-loader'
20
- }
20
+ },
21
+ ...(options.rules ?? [])
21
22
  ]
22
23
  },
23
24
  resolve: {