cotomy 1.0.3 → 1.0.5

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
@@ -243,7 +243,7 @@ The Form layer builds on `CotomyElement` for common form flows.
243
243
  - `loadActionUrl: string` — Defaults to `actionUrl`; override or set for custom endpoints
244
244
  - `canLoad: boolean` — Defaults to `hasEntityKey`
245
245
  - Naming & binding
246
- - `bindNameGenerator(): ICotomyBindNameGenerator` — Defaults to `CotomyBracketBindNameGenerator` (`user[name]`)
246
+ - `bindNameGenerator(): ICotomyBindNameGenerator` — Defaults to `CotomyViewRenderer.defaultBindNameGenerator` (initial value: `CotomyBracketBindNameGenerator`, `user[name]`)
247
247
  - `renderer(): CotomyViewRenderer` — Applies `[data-cotomy-bind]` to view elements
248
248
  - `filler(type, (input, value))` — Register fillers; defaults provided for `datetime-local`, `checkbox`, `radio`
249
249
  - Fills non-array, non-object fields by matching input/select/textarea `name`
@@ -255,15 +255,21 @@ The Form layer builds on `CotomyElement` for common form flows.
255
255
  - `mail`, `tel`, `url` — Wrap the value in a corresponding anchor tag.
256
256
  - `number` — Uses `Intl.NumberFormat` with `data-cotomy-locale`/`data-cotomy-currency` inheritance.
257
257
  - `data-cotomy-fraction-digits="2"` — Forces fixed fraction digits (sets both `minimumFractionDigits` and `maximumFractionDigits`). Works with or without `data-cotomy-currency` (e.g. `0` → `0.00`).
258
- - `utc` — Treats the value as UTC (or appends `Z` when missing) and formats with `data-cotomy-format` (default `YYYY/MM/DD HH:mm`).
259
- - `date` — Renders local dates with `data-cotomy-format` (default `YYYY/MM/DD`) when the input is a valid `Date` value.
258
+ - `utc` — Treats the value as UTC (or appends `Z` when missing) and formats with `data-cotomy-format` (default `YYYY/MM/DD HH:mm`). By default it renders in local time; set `data-cotomy-timezone` (element or ancestor) to render in a specific IANA timezone.
259
+ - `date` — Renders local dates with `data-cotomy-format` (default `YYYY/MM/DD`) when the input is a valid `Date` value. By default it renders in local time; set `data-cotomy-timezone` (element or ancestor) to render in a specific IANA timezone.
260
+
261
+ ### UTC Renderer
262
+
263
+ - Supports ISO 8601 offsets (`+09:00`, `-05:00`)
264
+ - Explicitly supports `Z` (UTC indicator)
265
+ - Offset-less timestamps are treated as UTC (internally appends "Z" before parsing)
266
+ - `data-cotomy-timezone` is optional. When omitted, output stays local; when set, output is converted to the specified IANA timezone (supports ancestor inheritance).
260
267
 
261
268
  Example:
262
269
 
263
270
  ```ts
264
271
  const view = new CotomyViewRenderer(
265
- new CotomyElement(document.querySelector("#profile")!),
266
- new CotomyBracketBindNameGenerator()
272
+ new CotomyElement(document.querySelector("#profile")!)
267
273
  );
268
274
 
269
275
  await view.applyAsync(apiResponse); // apiResponse is CotomyApiResponse from CotomyApi
@@ -289,6 +295,24 @@ form.apiFailed(e => console.error("API failed", e.response.status));
289
295
  form.submitFailed(e => console.warn("Submit failed", e.response.status));
290
296
  ```
291
297
 
298
+ To switch the default bind naming style for a page, set it from your page controller:
299
+
300
+ ```ts
301
+ import {
302
+ CotomyDotBindNameGenerator,
303
+ CotomyEntityFillApiForm,
304
+ CotomyPageController
305
+ } from "cotomy";
306
+
307
+ CotomyPageController.set(class extends CotomyPageController {
308
+ protected override async initializeAsync(): Promise<void> {
309
+ this.defaultBindNameGenerator = new CotomyDotBindNameGenerator();
310
+
311
+ this.setForm(CotomyEntityFillApiForm.byId("profile-form", CotomyEntityFillApiForm)!);
312
+ }
313
+ });
314
+ ```
315
+
292
316
  ### Entity API forms
293
317
 
294
318
  `CotomyEntityApiForm` targets REST endpoints that identify records with a single surrogate key.
@@ -289,6 +289,12 @@ __webpack_require__.d(__webpack_exports__, {
289
289
  // EXTERNAL MODULE: ./node_modules/dayjs/dayjs.min.js
290
290
  var dayjs_min = __webpack_require__(353);
291
291
  var dayjs_min_default = /*#__PURE__*/__webpack_require__.n(dayjs_min);
292
+ // EXTERNAL MODULE: ./node_modules/dayjs/plugin/timezone.js
293
+ var timezone = __webpack_require__(569);
294
+ var timezone_default = /*#__PURE__*/__webpack_require__.n(timezone);
295
+ // EXTERNAL MODULE: ./node_modules/dayjs/plugin/utc.js
296
+ var utc = __webpack_require__(826);
297
+ var utc_default = /*#__PURE__*/__webpack_require__.n(utc);
292
298
  ;// ./node_modules/http-status-codes/build/es/status-codes.js
293
299
  // Generated file. Do not edit
294
300
  var StatusCodes;
@@ -2257,6 +2263,10 @@ CotomyWindow._instance = null;
2257
2263
 
2258
2264
 
2259
2265
 
2266
+
2267
+
2268
+ dayjs_min_default().extend((utc_default()));
2269
+ dayjs_min_default().extend((timezone_default()));
2260
2270
  class CotomyApiException extends Error {
2261
2271
  constructor(status, message, response, bodyText = "") {
2262
2272
  super(message);
@@ -2452,19 +2462,20 @@ class CotomyDotBindNameGenerator {
2452
2462
  }
2453
2463
  }
2454
2464
  class CotomyViewRenderer {
2455
- constructor(element, bindNameGenerator) {
2465
+ static get defaultBindNameGenerator() {
2466
+ return this._defaultBindNameGenerator ?? new CotomyBracketBindNameGenerator();
2467
+ }
2468
+ static set defaultBindNameGenerator(generator) {
2469
+ this._defaultBindNameGenerator = generator;
2470
+ }
2471
+ static resetDefaultBindNameGenerator() {
2472
+ this._defaultBindNameGenerator = null;
2473
+ }
2474
+ constructor(element, bindNameGenerator = CotomyViewRenderer.defaultBindNameGenerator) {
2456
2475
  this.element = element;
2457
2476
  this.bindNameGenerator = bindNameGenerator;
2458
2477
  this._renderers = {};
2459
- this._builded = false;
2460
- }
2461
- get locale() {
2462
- const languages = (navigator.languages && navigator.languages.length ? navigator.languages : [navigator.language]).filter(Boolean);
2463
- let locale = this.element.attribute("data-cotomy-locale")
2464
- || this.element.closest("[data-cotomy-locale]")?.attribute("data-cotomy-locale")
2465
- || languages[0]
2466
- || 'en-US';
2467
- return locale.includes("-") ? locale.split("-")[0] : locale;
2478
+ this._initialized = false;
2468
2479
  }
2469
2480
  renderer(type, callback) {
2470
2481
  this._renderers[type] = callback;
@@ -2475,7 +2486,7 @@ class CotomyViewRenderer {
2475
2486
  return this._renderers;
2476
2487
  }
2477
2488
  get initialized() {
2478
- return this._builded;
2489
+ return this._initialized;
2479
2490
  }
2480
2491
  initialize() {
2481
2492
  if (!this.initialized) {
@@ -2507,16 +2518,27 @@ class CotomyViewRenderer {
2507
2518
  ...(currency ? { style: "currency", currency } : {}),
2508
2519
  ...(hasFractionDigits ? { minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits } : {}),
2509
2520
  };
2510
- element.text = new Intl.NumberFormat(this.locale, options).format(value);
2521
+ const languages = (navigator.languages && navigator.languages.length ? navigator.languages : [navigator.language]).filter(Boolean);
2522
+ const localeAttribute = element.attribute("data-cotomy-locale")
2523
+ || element.closest("[data-cotomy-locale]")?.attribute("data-cotomy-locale")
2524
+ || this.element.attribute("data-cotomy-locale")
2525
+ || this.element.closest("[data-cotomy-locale]")?.attribute("data-cotomy-locale")
2526
+ || languages[0]
2527
+ || "en-US";
2528
+ const locale = localeAttribute.includes("-") ? localeAttribute.split("-")[0] : localeAttribute;
2529
+ element.text = new Intl.NumberFormat(locale, options).format(value);
2511
2530
  }
2512
2531
  });
2513
2532
  this.renderer("utc", (element, value) => {
2514
2533
  if (value) {
2515
- const hasOffset = /[+-]\d{2}:\d{2}$/.test(value);
2534
+ const hasOffset = /([+-]\d{2}:\d{2}|Z)$/.test(value);
2516
2535
  const date = hasOffset ? new Date(value) : new Date(`${value}Z`);
2517
2536
  if (!isNaN(date.getTime())) {
2518
2537
  const format = element.attribute("data-cotomy-format") ?? "YYYY/MM/DD HH:mm";
2519
- element.text = dayjs_min_default()(date).format(format);
2538
+ const timezone = element.attribute("data-cotomy-timezone")
2539
+ || element.closest("[data-cotomy-timezone]")?.attribute("data-cotomy-timezone");
2540
+ const dt = dayjs_min_default()(date);
2541
+ element.text = timezone && timezone.trim() !== "" ? dt.tz(timezone).format(format) : dt.format(format);
2520
2542
  }
2521
2543
  }
2522
2544
  });
@@ -2525,11 +2547,14 @@ class CotomyViewRenderer {
2525
2547
  const date = new Date(value);
2526
2548
  if (!isNaN(date.getTime())) {
2527
2549
  const format = element.attribute("data-cotomy-format") ?? "YYYY/MM/DD";
2528
- element.text = dayjs_min_default()(date).format(format);
2550
+ const timezone = element.attribute("data-cotomy-timezone")
2551
+ || element.closest("[data-cotomy-timezone]")?.attribute("data-cotomy-timezone");
2552
+ const dt = dayjs_min_default()(date);
2553
+ element.text = timezone && timezone.trim() !== "" ? dt.tz(timezone).format(format) : dt.format(format);
2529
2554
  }
2530
2555
  }
2531
2556
  });
2532
- this._builded = true;
2557
+ this._initialized = true;
2533
2558
  }
2534
2559
  return this;
2535
2560
  }
@@ -2587,6 +2612,7 @@ class CotomyViewRenderer {
2587
2612
  return this;
2588
2613
  }
2589
2614
  }
2615
+ CotomyViewRenderer._defaultBindNameGenerator = null;
2590
2616
  class CotomyApi {
2591
2617
  constructor(_options = {
2592
2618
  baseUrl: null, headers: null, credentials: null, redirect: null,
@@ -3048,7 +3074,7 @@ class CotomyEntityFillApiForm extends CotomyEntityApiForm {
3048
3074
  return this.actionUrl;
3049
3075
  }
3050
3076
  bindNameGenerator() {
3051
- return new CotomyBracketBindNameGenerator();
3077
+ return CotomyViewRenderer.defaultBindNameGenerator;
3052
3078
  }
3053
3079
  renderer() {
3054
3080
  return new CotomyViewRenderer(this, this.bindNameGenerator());
@@ -3140,18 +3166,13 @@ class CotomyEntityFillApiForm extends CotomyEntityApiForm {
3140
3166
  }
3141
3167
  }
3142
3168
 
3143
- // EXTERNAL MODULE: ./node_modules/dayjs/plugin/timezone.js
3144
- var timezone = __webpack_require__(569);
3145
- var timezone_default = /*#__PURE__*/__webpack_require__.n(timezone);
3146
- // EXTERNAL MODULE: ./node_modules/dayjs/plugin/utc.js
3147
- var utc = __webpack_require__(826);
3148
- var utc_default = /*#__PURE__*/__webpack_require__.n(utc);
3149
3169
  ;// ./src/page.ts
3150
3170
 
3151
3171
 
3152
3172
 
3153
3173
 
3154
3174
 
3175
+
3155
3176
  dayjs_min_default().extend((utc_default()));
3156
3177
  dayjs_min_default().extend((timezone_default()));
3157
3178
  class CotomyUrl {
@@ -3213,6 +3234,12 @@ class CotomyPageController {
3213
3234
  }
3214
3235
  return this._instance;
3215
3236
  }
3237
+ get defaultBindNameGenerator() {
3238
+ return CotomyViewRenderer.defaultBindNameGenerator;
3239
+ }
3240
+ set defaultBindNameGenerator(value) {
3241
+ CotomyViewRenderer.defaultBindNameGenerator = value;
3242
+ }
3216
3243
  setForm(form) {
3217
3244
  if (!form.id) {
3218
3245
  form.generateId();