@simai-studio/view 1.3.0 → 1.4.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 +4 -4
- package/dist/package-receipt.json +106 -106
- package/dist/set.json +1 -1
- package/dist/smart/view/component.json +19 -10
- package/dist/smart/view/component.json.gz +0 -0
- package/dist/smart/view/js/view.js +2 -2
- package/dist/smart/view/js/view.js.gz +0 -0
- package/dist/smart/view/js/view.min.js +1 -1
- package/dist/smart/view/js/view.min.js.gz +0 -0
- package/dist/smart/view/smart.manifest.json +14 -6
- package/dist/smart/view/smart.manifest.json.gz +0 -0
- package/dist/smart/view/static/default/open-record.html +1 -1
- package/dist/smart/view/static/default/open-record.html.gz +0 -0
- package/dist/smart/view/static/default/reference.html +1 -1
- package/dist/smart/view/static/default/reference.html.gz +0 -0
- package/dist/smart/view-flow/js/view-flow.js +388 -31
- package/dist/smart/view-flow/js/view-flow.js.gz +0 -0
- package/dist/smart/view-flow/js/view-flow.min.js +213 -201
- package/dist/smart/view-flow/js/view-flow.min.js.gz +0 -0
- package/dist/smart/view-flow/static/default/ask.html +1 -1
- package/dist/smart/view-flow/static/default/ask.html.gz +0 -0
- package/dist/smart/view-flow/static/default/reference.html +2 -2
- package/dist/smart/view-flow/static/default/reference.html.gz +0 -0
- package/dist/smart/view-properties/component.json +8 -1
- package/dist/smart/view-properties/component.json.gz +0 -0
- package/dist/smart/view-properties/css/view-properties.css +17 -4
- package/dist/smart/view-properties/css/view-properties.css.gz +0 -0
- package/dist/smart/view-properties/css/view-properties.min.css +1 -1
- package/dist/smart/view-properties/css/view-properties.min.css.gz +0 -0
- package/dist/smart/view-properties/js/view-properties.js +389 -32
- package/dist/smart/view-properties/js/view-properties.js.gz +0 -0
- package/dist/smart/view-properties/js/view-properties.min.js +167 -155
- package/dist/smart/view-properties/js/view-properties.min.js.gz +0 -0
- package/dist/smart/view-properties/smart.manifest.json +9 -2
- package/dist/smart/view-properties/smart.manifest.json.gz +0 -0
- package/dist/smart/view-properties/template/default.css +13 -2
- package/dist/smart/view-properties/template/default.css.gz +0 -0
- package/dist/smart/view-table/component.json +8 -1
- package/dist/smart/view-table/component.json.gz +0 -0
- package/dist/smart/view-table/js/view-table.js +393 -33
- package/dist/smart/view-table/js/view-table.js.gz +0 -0
- package/dist/smart/view-table/js/view-table.min.js +152 -139
- package/dist/smart/view-table/js/view-table.min.js.gz +0 -0
- package/dist/smart/view-table/smart.manifest.json +9 -2
- package/dist/smart/view-table/smart.manifest.json.gz +0 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"id": "smart.view",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.9.0",
|
|
5
5
|
"title": "Data view",
|
|
6
6
|
"summary": "Host-driven data view that composes toolbar, content and pagination around a separate data port.",
|
|
7
7
|
"kind": "composite",
|
|
@@ -143,9 +143,9 @@
|
|
|
143
143
|
],
|
|
144
144
|
"host_port": {
|
|
145
145
|
"port": "simai.dataview-port",
|
|
146
|
-
"version": "1.
|
|
147
|
-
"path": "src/core/contracts/ports/simai.dataview-port/1.
|
|
148
|
-
"sha256": "
|
|
146
|
+
"version": "1.14.0",
|
|
147
|
+
"path": "src/core/contracts/ports/simai.dataview-port/1.14.0/port.json",
|
|
148
|
+
"sha256": "729e7aae0fa1f4425ae11a470de024f75e92d475cd24cc63b7e1d3d020d2955b",
|
|
149
149
|
"supported_versions": [
|
|
150
150
|
"1.0.0",
|
|
151
151
|
"1.1.0",
|
|
@@ -160,7 +160,8 @@
|
|
|
160
160
|
"1.10.0",
|
|
161
161
|
"1.11.0",
|
|
162
162
|
"1.12.0",
|
|
163
|
-
"1.13.0"
|
|
163
|
+
"1.13.0",
|
|
164
|
+
"1.14.0"
|
|
164
165
|
]
|
|
165
166
|
}
|
|
166
167
|
}
|
|
@@ -1521,6 +1522,13 @@
|
|
|
1521
1522
|
"changes": [
|
|
1522
1523
|
"A table the host wrote selectable=\"false\" or settings=\"false\" on keeps it inside the view (no row boxes, no settings column); before, the view's chrome overwrote both. Nothing to change for a host; a host that wanted the view's boxes on such a table removes its own attribute."
|
|
1523
1524
|
]
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
"from": "1.8.2",
|
|
1528
|
+
"to": "1.9.0",
|
|
1529
|
+
"changes": [
|
|
1530
|
+
"Speaks simai.dataview-port 1.14.0 (SIMAI Framework pair ui-0c23f6a0af12, ui-source #48) beside 1.0.0-1.13.0: a bulk.apply_selected the host refuses with confirm_required and a digest is asked of the person and, confirmed, raised again with payload.confirm, as record.mutate and record.set already were. Nothing to change for a host on 1.13.0 or earlier; a host on 1.14.0 may ask to confirm a bulk write first."
|
|
1531
|
+
]
|
|
1524
1532
|
}
|
|
1525
1533
|
]
|
|
1526
1534
|
},
|
|
@@ -1530,7 +1538,7 @@
|
|
|
1530
1538
|
"source_artifacts": [
|
|
1531
1539
|
{
|
|
1532
1540
|
"path": "src/smart/view/index.js",
|
|
1533
|
-
"sha256": "
|
|
1541
|
+
"sha256": "2aa16167dbc9ea866914aec36ad6e8738c4d6c7b1f121cf0e2cb0c6dd5d75c34"
|
|
1534
1542
|
},
|
|
1535
1543
|
{
|
|
1536
1544
|
"path": "src/core/contracts/ports/simai.dataview-port/1.5.0/port.json",
|
|
Binary file
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
</button>
|
|
230
230
|
</label>
|
|
231
231
|
|
|
232
|
-
<div class="sf-list flex flex-col" style="--sf-list--cell: var(--sf-ui-1--control-height)" hidden></div>
|
|
232
|
+
<div class="sf-list sf-list--size-1 flex flex-col" style="--sf-list--cell: var(--sf-ui-1--control-height)" hidden></div>
|
|
233
233
|
<span class="sf-dropdown-value" id="sf-view-open-record-3">25</span></div>
|
|
234
234
|
</sf-dropdown>
|
|
235
235
|
</div>
|
|
Binary file
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
</button>
|
|
230
230
|
</label>
|
|
231
231
|
|
|
232
|
-
<div class="sf-list flex flex-col" style="--sf-list--cell: var(--sf-ui-1--control-height)" hidden></div>
|
|
232
|
+
<div class="sf-list sf-list--size-1 flex flex-col" style="--sf-list--cell: var(--sf-ui-1--control-height)" hidden></div>
|
|
233
233
|
<span class="sf-dropdown-value" id="sf-view-reference-3">25</span></div>
|
|
234
234
|
</sf-dropdown>
|
|
235
235
|
</div>
|
|
Binary file
|
|
@@ -407,6 +407,173 @@ function composeTime(original, text) {
|
|
|
407
407
|
|
|
408
408
|
/***/ },
|
|
409
409
|
|
|
410
|
+
/***/ "ab3947ea3647"
|
|
411
|
+
(module, __webpack_exports__, __webpack_require__) {
|
|
412
|
+
|
|
413
|
+
__webpack_require__.a(module, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try {
|
|
414
|
+
__webpack_require__.r(__webpack_exports__);
|
|
415
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
416
|
+
/* harmony export */ formatDecimal: () => (/* binding */ formatDecimal),
|
|
417
|
+
/* harmony export */ formatMoney: () => (/* binding */ formatMoney),
|
|
418
|
+
/* harmony export */ formatPercent: () => (/* binding */ formatPercent),
|
|
419
|
+
/* harmony export */ fractionDigits: () => (/* binding */ fractionDigits),
|
|
420
|
+
/* harmony export */ moneyParts: () => (/* binding */ moneyParts),
|
|
421
|
+
/* harmony export */ readDecimal: () => (/* binding */ readDecimal)
|
|
422
|
+
/* harmony export */ });
|
|
423
|
+
/* harmony import */ var _core_js_format_intl_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("15b4e93ff6ed");
|
|
424
|
+
var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([_core_js_format_intl_js__WEBPACK_IMPORTED_MODULE_0__]);
|
|
425
|
+
var __webpack_async_dependencies_result__ = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__);
|
|
426
|
+
_core_js_format_intl_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_async_dependencies_result__[0];
|
|
427
|
+
// Decimal numbers as strings, never as floats (simai.property-view-port
|
|
428
|
+
// 1.18.0; Larena property types standard, notes of 2026-11-03: «Never a float
|
|
429
|
+
// on the wire»). What a person typed is read into a canonical decimal string,
|
|
430
|
+
// and a decimal, a money amount and a percent are shown in the page's language
|
|
431
|
+
// through Intl, which formats a string by its digits: «1500.10» never passes
|
|
432
|
+
// through a float on the way to «1 500,10 ₽». Pure, so the unit suite reads it
|
|
433
|
+
// without a browser; the View set draws the editors of money, percent and
|
|
434
|
+
// items with its own copy of the same rules.
|
|
435
|
+
|
|
436
|
+
const TYPED = /^([+-]?)(\d*)(?:[.,](\d*))?$/u; // Spaces a person or a formatter puts between groups of digits: the plain
|
|
437
|
+
// space, the no-break space and the narrow no-break space ru-RU writes, and
|
|
438
|
+
// the apostrophe some locales group with.
|
|
439
|
+
|
|
440
|
+
const GROUP_SPACES = /[\s ']/gu;
|
|
441
|
+
/**
|
|
442
|
+
* What a person typed, read as a decimal: `{ok: true, value}` with the
|
|
443
|
+
* canonical string (an optional minus, the integer digits without leading
|
|
444
|
+
* zeros, the fraction digits as typed; null for nothing typed), or
|
|
445
|
+
* `{ok: false}` for text that is not a number. A comma and a point both
|
|
446
|
+
* separate the fraction. A JSON integer is read as its digits; a float is not
|
|
447
|
+
* a decimal and is refused rather than guessed at.
|
|
448
|
+
*/
|
|
449
|
+
|
|
450
|
+
function readDecimal(text) {
|
|
451
|
+
if (text === null || typeof text === 'undefined') return {
|
|
452
|
+
ok: true,
|
|
453
|
+
value: null
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
if (typeof text === 'number') {
|
|
457
|
+
return Number.isSafeInteger(text) ? {
|
|
458
|
+
ok: true,
|
|
459
|
+
value: String(text)
|
|
460
|
+
} : {
|
|
461
|
+
ok: false
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const plain = String(text).replace(GROUP_SPACES, '');
|
|
466
|
+
if (plain === '') return {
|
|
467
|
+
ok: true,
|
|
468
|
+
value: null
|
|
469
|
+
};
|
|
470
|
+
const match = TYPED.exec(plain);
|
|
471
|
+
if (!match) return {
|
|
472
|
+
ok: false
|
|
473
|
+
};
|
|
474
|
+
const [, sign, whole, fraction] = match;
|
|
475
|
+
if (whole === '' && (fraction ?? '') === '') return {
|
|
476
|
+
ok: false
|
|
477
|
+
};
|
|
478
|
+
const integer = whole.replace(/^0+(?=\d)/u, '') || '0';
|
|
479
|
+
const digits = fraction ?? '';
|
|
480
|
+
const zero = /^0*$/u.test(integer + digits);
|
|
481
|
+
return {
|
|
482
|
+
ok: true,
|
|
483
|
+
value: `${sign === '-' && !zero ? '-' : ''}${integer}${digits ? `.${digits}` : ''}`
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
/** How many digits a canonical decimal string has after its point. */
|
|
487
|
+
|
|
488
|
+
function fractionDigits(value) {
|
|
489
|
+
const at = String(value ?? '').indexOf('.');
|
|
490
|
+
return at === -1 ? 0 : String(value).length - at - 1;
|
|
491
|
+
} // A float a host sent anyway is shown, not refused: a cell is not the place to
|
|
492
|
+
// argue with a stored value. Only what reads as a decimal is formatted.
|
|
493
|
+
|
|
494
|
+
function decimalOf(value) {
|
|
495
|
+
if (typeof value === 'number') return Number.isFinite(value) ? String(value) : null;
|
|
496
|
+
const read = readDecimal(value);
|
|
497
|
+
return read.ok ? read.value : null;
|
|
498
|
+
}
|
|
499
|
+
/** A decimal string in the locale's digits, with every digit it has: «1 500,5». */
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
function formatDecimal(value, locale) {
|
|
503
|
+
const decimal = decimalOf(value);
|
|
504
|
+
if (decimal === null) return '';
|
|
505
|
+
const digits = Math.min(20, fractionDigits(decimal));
|
|
506
|
+
return (0,_core_js_format_intl_js__WEBPACK_IMPORTED_MODULE_0__.intlFormatter)('NumberFormat', locale, {
|
|
507
|
+
minimumFractionDigits: digits,
|
|
508
|
+
maximumFractionDigits: digits
|
|
509
|
+
}).format(decimal);
|
|
510
|
+
}
|
|
511
|
+
/** A money value as {amount, currency}; a field that fixes the currency takes a bare amount. */
|
|
512
|
+
|
|
513
|
+
function moneyParts(value, fixed) {
|
|
514
|
+
const currency = typeof fixed === 'string' && fixed ? fixed : null;
|
|
515
|
+
|
|
516
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
517
|
+
return {
|
|
518
|
+
amount: value.amount ?? null,
|
|
519
|
+
currency: currency || (value.currency ?? null)
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (value === null || typeof value === 'undefined' || value === '') return {
|
|
524
|
+
amount: null,
|
|
525
|
+
currency
|
|
526
|
+
};
|
|
527
|
+
return {
|
|
528
|
+
amount: value,
|
|
529
|
+
currency
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* An amount with its currency, as a person reads it: «1 500,50 ₽», «$1,500.50».
|
|
534
|
+
* The digits after the point are at least the currency's minor units (ISO
|
|
535
|
+
* 4217) and every digit the amount has. Without a currency, the amount alone.
|
|
536
|
+
*/
|
|
537
|
+
|
|
538
|
+
function formatMoney(value, field = {}, locale = 'en') {
|
|
539
|
+
const {
|
|
540
|
+
amount,
|
|
541
|
+
currency
|
|
542
|
+
} = moneyParts(value, field.currency);
|
|
543
|
+
const decimal = decimalOf(amount);
|
|
544
|
+
if (decimal === null) return '';
|
|
545
|
+
if (!currency) return formatDecimal(decimal, locale);
|
|
546
|
+
const minor = (0,_core_js_format_intl_js__WEBPACK_IMPORTED_MODULE_0__.intlFormatter)('NumberFormat', locale, {
|
|
547
|
+
style: 'currency',
|
|
548
|
+
currency
|
|
549
|
+
}).resolvedOptions().maximumFractionDigits ?? 2;
|
|
550
|
+
const digits = Math.min(20, Math.max(minor, fractionDigits(decimal)));
|
|
551
|
+
return (0,_core_js_format_intl_js__WEBPACK_IMPORTED_MODULE_0__.intlFormatter)('NumberFormat', locale, {
|
|
552
|
+
style: 'currency',
|
|
553
|
+
currency,
|
|
554
|
+
currencyDisplay: 'narrowSymbol',
|
|
555
|
+
minimumFractionDigits: digits,
|
|
556
|
+
maximumFractionDigits: digits
|
|
557
|
+
}).format(decimal);
|
|
558
|
+
}
|
|
559
|
+
/** A percent, «12,5 %» or «12.5%»: the value is the percent ("20" is 20 %), never the share. */
|
|
560
|
+
|
|
561
|
+
function formatPercent(value, locale = 'en') {
|
|
562
|
+
const decimal = decimalOf(value);
|
|
563
|
+
if (decimal === null) return '';
|
|
564
|
+
const digits = Math.min(20, fractionDigits(decimal));
|
|
565
|
+
return (0,_core_js_format_intl_js__WEBPACK_IMPORTED_MODULE_0__.intlFormatter)('NumberFormat', locale, {
|
|
566
|
+
style: 'unit',
|
|
567
|
+
unit: 'percent',
|
|
568
|
+
minimumFractionDigits: digits,
|
|
569
|
+
maximumFractionDigits: digits
|
|
570
|
+
}).format(decimal);
|
|
571
|
+
}
|
|
572
|
+
__webpack_async_result__();
|
|
573
|
+
} catch(e) { __webpack_async_result__(e); } });
|
|
574
|
+
|
|
575
|
+
/***/ },
|
|
576
|
+
|
|
410
577
|
/***/ "01ae054f01b4"
|
|
411
578
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
412
579
|
|
|
@@ -473,8 +640,9 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
473
640
|
/* harmony import */ var _datetime_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__("9ce1a42b2a85");
|
|
474
641
|
/* harmony import */ var _mask_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__("dd6973f77245");
|
|
475
642
|
/* harmony import */ var _html_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__("01ae054f01b4");
|
|
476
|
-
var
|
|
477
|
-
([lit__WEBPACK_IMPORTED_MODULE_0__, lit_directives_unsafe_html_js__WEBPACK_IMPORTED_MODULE_1__, _core_js_page_language__WEBPACK_IMPORTED_MODULE_3__]
|
|
643
|
+
/* harmony import */ var _decimal_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__("ab3947ea3647");
|
|
644
|
+
var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([lit__WEBPACK_IMPORTED_MODULE_0__, lit_directives_unsafe_html_js__WEBPACK_IMPORTED_MODULE_1__, _core_js_page_language__WEBPACK_IMPORTED_MODULE_3__, _decimal_js__WEBPACK_IMPORTED_MODULE_8__]);
|
|
645
|
+
([lit__WEBPACK_IMPORTED_MODULE_0__, lit_directives_unsafe_html_js__WEBPACK_IMPORTED_MODULE_1__, _core_js_page_language__WEBPACK_IMPORTED_MODULE_3__, _decimal_js__WEBPACK_IMPORTED_MODULE_8__] = __webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__);
|
|
478
646
|
/* harmony import */ var lit__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(lit__WEBPACK_IMPORTED_MODULE_0__);
|
|
479
647
|
/* harmony import */ var lit_directives_unsafe_html_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lit_directives_unsafe_html_js__WEBPACK_IMPORTED_MODULE_1__);
|
|
480
648
|
/* harmony import */ var _core_js_page_language__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_core_js_page_language__WEBPACK_IMPORTED_MODULE_3__);
|
|
@@ -485,6 +653,7 @@ var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([lit_
|
|
|
485
653
|
|
|
486
654
|
|
|
487
655
|
|
|
656
|
+
|
|
488
657
|
// The Framework's field renderers, addressed by a property type and a template
|
|
489
658
|
// rather than by a control name.
|
|
490
659
|
//
|
|
@@ -844,7 +1013,13 @@ const FILTER_RENDERERS = {
|
|
|
844
1013
|
relation: renderEntityMultiSelectFilterControl,
|
|
845
1014
|
lookup: renderEntityMultiSelectFilterControl,
|
|
846
1015
|
file: renderFileFilterControl,
|
|
847
|
-
color: renderColorFilterControl
|
|
1016
|
+
color: renderColorFilterControl,
|
|
1017
|
+
// The types a component set draws (property-view port 1.18.0): a money
|
|
1018
|
+
// amount and a percent are filtered as numbers (the host compares the
|
|
1019
|
+
// amount, as a decimal), and an items field by its number of rows.
|
|
1020
|
+
money: renderNumberFilterControl,
|
|
1021
|
+
percent: renderNumberFilterControl,
|
|
1022
|
+
items: renderNumberFilterControl
|
|
848
1023
|
}; // What a field asks to be filtered by: its own declared control if it has one,
|
|
849
1024
|
// otherwise its property type, otherwise text.
|
|
850
1025
|
|
|
@@ -1098,7 +1273,10 @@ const CELL_RENDERERS = {
|
|
|
1098
1273
|
record: renderReferenceCell,
|
|
1099
1274
|
relation: renderReferenceCell,
|
|
1100
1275
|
lookup: renderLookupCell,
|
|
1101
|
-
html: renderHtmlCell
|
|
1276
|
+
html: renderHtmlCell,
|
|
1277
|
+
money: renderMoneyCell,
|
|
1278
|
+
percent: renderPercentCell,
|
|
1279
|
+
items: renderItemsCell
|
|
1102
1280
|
}; // A consumer found this on their stand: the words were written into the set, so
|
|
1103
1281
|
// an English admin saw «Да». The set must not decide what a value is called in
|
|
1104
1282
|
// anyone's language. They come from the field, which is where the rest of the
|
|
@@ -1158,6 +1336,41 @@ function renderChoiceCell(field, value) {
|
|
|
1158
1336
|
function renderHostFormattedCell(field, value, host) {
|
|
1159
1337
|
const formatted = host?.formatFieldValue?.(field, value);
|
|
1160
1338
|
return typeof formatted === 'string' && formatted !== '' ? formatted : null;
|
|
1339
|
+
} // The types a component set draws (property-view port 1.18.0) have their
|
|
1340
|
+
// cells here, so a table shows them wherever the set's editors live. A host's
|
|
1341
|
+
// formatFieldValue wins, as for a date; otherwise the page's language:
|
|
1342
|
+
//
|
|
1343
|
+
// money {amount, currency} or a bare amount under a fixed `currency`;
|
|
1344
|
+
// «1 500,50 ₽», every digit the amount has and at least the
|
|
1345
|
+
// currency's minor units, formatted from the digits, never a float
|
|
1346
|
+
// percent a decimal string of the percent, «12,5 %»
|
|
1347
|
+
// items a list of rows; how many, «3 строки»
|
|
1348
|
+
//
|
|
1349
|
+
// A value that is not one of these shapes falls through to the plain value.
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
function renderMoneyCell(field, value, host) {
|
|
1353
|
+
const formatted = renderHostFormattedCell(field, value, host);
|
|
1354
|
+
if (formatted !== null) return formatted;
|
|
1355
|
+
const text = (0,_decimal_js__WEBPACK_IMPORTED_MODULE_8__.formatMoney)(scalarValue(value), field, (0,_core_js_page_language__WEBPACK_IMPORTED_MODULE_3__.pageLocale)(host ?? null));
|
|
1356
|
+
return text || null;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function renderPercentCell(field, value, host) {
|
|
1360
|
+
const formatted = renderHostFormattedCell(field, value, host);
|
|
1361
|
+
if (formatted !== null) return formatted;
|
|
1362
|
+
const text = (0,_decimal_js__WEBPACK_IMPORTED_MODULE_8__.formatPercent)(scalarValue(value), (0,_core_js_page_language__WEBPACK_IMPORTED_MODULE_3__.pageLocale)(host ?? null));
|
|
1363
|
+
return text || null;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function renderItemsCell(field, value, host) {
|
|
1367
|
+
const formatted = renderHostFormattedCell(field, value, host);
|
|
1368
|
+
if (formatted !== null) return formatted;
|
|
1369
|
+
if (!Array.isArray(value)) return null;
|
|
1370
|
+
return value.length ? (0,_core_js_page_language__WEBPACK_IMPORTED_MODULE_3__.pageWord)(SET_TEXT, 'rowCount', host ?? null, {
|
|
1371
|
+
component: SET_NAME,
|
|
1372
|
+
count: value.length
|
|
1373
|
+
}) : '';
|
|
1161
1374
|
}
|
|
1162
1375
|
|
|
1163
1376
|
function renderNumericCell(field, value, host) {
|
|
@@ -1332,10 +1545,14 @@ function renderPhoneEdit(field, value, host) {
|
|
|
1332
1545
|
// Clearing a set password is not offered: an empty field means «keep».
|
|
1333
1546
|
//
|
|
1334
1547
|
// The typed text lives in the input and nowhere else: no `value` attribute on
|
|
1335
|
-
// sf-input, no data-*, no text node, no log.
|
|
1336
|
-
// switches the input's type and says
|
|
1337
|
-
//
|
|
1338
|
-
//
|
|
1548
|
+
// sf-input, no data-*, no text node, no log. «Показать пароль» is an icon button
|
|
1549
|
+
// that switches the input's type and says what it will do next by its name and
|
|
1550
|
+
// icon: «Показать пароль» (visibility) while hidden, «Скрыть пароль»
|
|
1551
|
+
// (visibility_off) while shown. A toggle whose name changes is not a pressed
|
|
1552
|
+
// toggle (rule C12, point 3): it carries no aria-pressed and no pressed look,
|
|
1553
|
+
// or a reader would hear «Скрыть пароль, нажата». It changes the DOM directly,
|
|
1554
|
+
// so a host's re-render (its attributes are static in the template) leaves the
|
|
1555
|
+
// person's choice as it is.
|
|
1339
1556
|
|
|
1340
1557
|
|
|
1341
1558
|
function isPasswordField(field) {
|
|
@@ -1351,13 +1568,13 @@ function passwordState(host, value) {
|
|
|
1351
1568
|
return setText(host, passwordIsSet(value) ? 'passwordSet' : 'passwordUnset');
|
|
1352
1569
|
}
|
|
1353
1570
|
|
|
1354
|
-
function togglePasswordShown(event) {
|
|
1571
|
+
function togglePasswordShown(event, host) {
|
|
1355
1572
|
const button = event.currentTarget;
|
|
1356
1573
|
const input = button?.closest?.('.sf-field-password')?.querySelector?.('sf-input');
|
|
1357
1574
|
if (!input) return;
|
|
1358
|
-
const shown =
|
|
1575
|
+
const shown = input.getAttribute('input-type') !== 'text';
|
|
1359
1576
|
input.setAttribute('input-type', shown ? 'text' : 'password');
|
|
1360
|
-
button.setAttribute('
|
|
1577
|
+
button.setAttribute('aria-label', setText(host, shown ? 'hidePassword' : 'showPassword'));
|
|
1361
1578
|
button.setAttribute('icon', shown ? 'visibility_off' : 'visibility');
|
|
1362
1579
|
}
|
|
1363
1580
|
|
|
@@ -1393,9 +1610,8 @@ function renderPasswordEdit(field, value, host) {
|
|
|
1393
1610
|
appearance="link"
|
|
1394
1611
|
scheme="on-surface"
|
|
1395
1612
|
icon="visibility"
|
|
1396
|
-
pressed="false"
|
|
1397
1613
|
aria-label=${setText(host, 'showPassword')}
|
|
1398
|
-
@click=${togglePasswordShown}
|
|
1614
|
+
@click=${event => togglePasswordShown(event, host)}
|
|
1399
1615
|
></sf-button-icon>
|
|
1400
1616
|
</span>
|
|
1401
1617
|
`;
|
|
@@ -1658,29 +1874,98 @@ function renderStepperEdit(field, value, host) {
|
|
|
1658
1874
|
@change=${write}
|
|
1659
1875
|
></sf-input-quantity>
|
|
1660
1876
|
`;
|
|
1877
|
+
} // A number is a decimal (property-view port 1.18.0: «never a float on the
|
|
1878
|
+
// wire»). What is typed is read with a comma or a point and spaces between
|
|
1879
|
+
// groups. A number field whose value arrives as a string, or that declares
|
|
1880
|
+
// `precision`, is a decimal-string field: it writes the canonical decimal
|
|
1881
|
+
// string («1500.50»), never a float, and refuses more digits after the point
|
|
1882
|
+
// than `precision` allows. Any other number field writes a number, as before;
|
|
1883
|
+
// an integer field is unchanged. Text that is not a number is reported as a
|
|
1884
|
+
// masked string's incomplete entry is: through setFieldError, which holds the
|
|
1885
|
+
// save, and under the field once the person leaves it.
|
|
1886
|
+
|
|
1887
|
+
|
|
1888
|
+
function isDecimalStringField(field, value) {
|
|
1889
|
+
return (field.propertyType || field.type) === 'number' && (typeof value === 'string' || Number.isInteger(field.precision));
|
|
1661
1890
|
}
|
|
1662
1891
|
|
|
1663
1892
|
function renderNumberEdit(field, value, host) {
|
|
1664
1893
|
if (isStepperField(field)) return renderStepperEdit(field, value, host);
|
|
1665
1894
|
const decimal = (field.propertyType || field.type) === 'number';
|
|
1666
1895
|
value = scalarValue(value);
|
|
1896
|
+
|
|
1897
|
+
if (!decimal) {
|
|
1898
|
+
return (0,lit__WEBPACK_IMPORTED_MODULE_0__.html)`
|
|
1899
|
+
<sf-input
|
|
1900
|
+
size=${EDIT_SIZE}
|
|
1901
|
+
appearance="bordered"
|
|
1902
|
+
name=${fieldName(field)}
|
|
1903
|
+
value=${value ?? ''}
|
|
1904
|
+
inputmode="numeric"
|
|
1905
|
+
?required=${Boolean(field.required)}
|
|
1906
|
+
aria-label=${field.label || field.key || ''}
|
|
1907
|
+
@input=${event => {
|
|
1908
|
+
const next = event.target?.value ?? '';
|
|
1909
|
+
host.setFieldValue(field, next === '' ? null : Number(next));
|
|
1910
|
+
}}
|
|
1911
|
+
@change=${event => {
|
|
1912
|
+
const next = event.target?.value ?? '';
|
|
1913
|
+
host.setFieldValue(field, next === '' ? null : Number(next));
|
|
1914
|
+
}}
|
|
1915
|
+
></sf-input>
|
|
1916
|
+
`;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
const asString = isDecimalStringField(field, value);
|
|
1920
|
+
|
|
1921
|
+
const write = (event, left) => {
|
|
1922
|
+
const input = event.currentTarget ?? event.target;
|
|
1923
|
+
const text = String(input?.value ?? '');
|
|
1924
|
+
const read = (0,_decimal_js__WEBPACK_IMPORTED_MODULE_8__.readDecimal)(text);
|
|
1925
|
+
let message = null;
|
|
1926
|
+
if (!read.ok) message = setText(host, 'notNumber');else if (read.value !== null && Number.isInteger(field.precision) && (0,_decimal_js__WEBPACK_IMPORTED_MODULE_8__.fractionDigits)(read.value) > field.precision) {
|
|
1927
|
+
message = (0,_core_js_page_language__WEBPACK_IMPORTED_MODULE_3__.pageWord)(SET_TEXT, 'precisionMax', host ?? null, {
|
|
1928
|
+
component: SET_NAME,
|
|
1929
|
+
count: field.precision
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
if (message) {
|
|
1934
|
+
if (left && input) {
|
|
1935
|
+
input.setAttribute('error-message', message);
|
|
1936
|
+
input.toggleAttribute('invalid', true);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
if (typeof host.setFieldError === 'function') {
|
|
1940
|
+
host.setFieldError(field, message);
|
|
1941
|
+
host.setFieldValue(field, text);
|
|
1942
|
+
} else {
|
|
1943
|
+
host.setFieldValue(field, undefined);
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
if (input?.hasAttribute?.('invalid')) {
|
|
1950
|
+
input.toggleAttribute('invalid', false);
|
|
1951
|
+
input.removeAttribute('error-message');
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
host.setFieldError?.(field, null);
|
|
1955
|
+
if (read.value === null) host.setFieldValue(field, null);else host.setFieldValue(field, asString ? read.value : Number(read.value));
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1667
1958
|
return (0,lit__WEBPACK_IMPORTED_MODULE_0__.html)`
|
|
1668
1959
|
<sf-input
|
|
1669
1960
|
size=${EDIT_SIZE}
|
|
1670
1961
|
appearance="bordered"
|
|
1671
1962
|
name=${fieldName(field)}
|
|
1672
1963
|
value=${value ?? ''}
|
|
1673
|
-
inputmode
|
|
1964
|
+
inputmode="decimal"
|
|
1674
1965
|
?required=${Boolean(field.required)}
|
|
1675
1966
|
aria-label=${field.label || field.key || ''}
|
|
1676
|
-
@input=${event =>
|
|
1677
|
-
|
|
1678
|
-
host.setFieldValue(field, next === '' ? null : Number(next));
|
|
1679
|
-
}}
|
|
1680
|
-
@change=${event => {
|
|
1681
|
-
const next = event.target?.value ?? '';
|
|
1682
|
-
host.setFieldValue(field, next === '' ? null : Number(next));
|
|
1683
|
-
}}
|
|
1967
|
+
@input=${event => write(event, false)}
|
|
1968
|
+
@change=${event => write(event, true)}
|
|
1684
1969
|
></sf-input>
|
|
1685
1970
|
`;
|
|
1686
1971
|
} // A date control has no clock. `sf-input-date` declares a format and a date,
|
|
@@ -2123,7 +2408,14 @@ const SET_TEXT = {
|
|
|
2123
2408
|
passwordUnset: 'Not set',
|
|
2124
2409
|
passwordKeep: 'Set; type a new one to replace it',
|
|
2125
2410
|
showPassword: 'Show password',
|
|
2126
|
-
|
|
2411
|
+
hidePassword: 'Hide password',
|
|
2412
|
+
maskIncomplete: 'Complete the field',
|
|
2413
|
+
notNumber: 'Enter a number',
|
|
2414
|
+
precisionMax: 'At most {n} digits after the point',
|
|
2415
|
+
rowCount: {
|
|
2416
|
+
one: '{n} row',
|
|
2417
|
+
other: '{n} rows'
|
|
2418
|
+
}
|
|
2127
2419
|
},
|
|
2128
2420
|
ru: {
|
|
2129
2421
|
yes: 'Да',
|
|
@@ -2151,7 +2443,16 @@ const SET_TEXT = {
|
|
|
2151
2443
|
passwordUnset: 'Не задан',
|
|
2152
2444
|
passwordKeep: 'Задан; введите новый, чтобы заменить',
|
|
2153
2445
|
showPassword: 'Показать пароль',
|
|
2154
|
-
|
|
2446
|
+
hidePassword: 'Скрыть пароль',
|
|
2447
|
+
maskIncomplete: 'Заполните поле полностью',
|
|
2448
|
+
notNumber: 'Введите число',
|
|
2449
|
+
precisionMax: 'Не больше {n} знаков после запятой',
|
|
2450
|
+
rowCount: {
|
|
2451
|
+
one: '{n} строка',
|
|
2452
|
+
few: '{n} строки',
|
|
2453
|
+
many: '{n} строк',
|
|
2454
|
+
other: '{n} строки'
|
|
2455
|
+
}
|
|
2155
2456
|
}
|
|
2156
2457
|
}; // The host's word wins, as it always did; the set's own fallback now follows
|
|
2157
2458
|
// the page's language instead of being Russian whatever the page is. `host` is
|
|
@@ -2668,10 +2969,13 @@ function renderChoiceView(field, value) {
|
|
|
2668
2969
|
const options = field.options || field.filter?.options || field.constraints?.options;
|
|
2669
2970
|
const option = Array.isArray(options) ? options.find(item => String(item?.value) === String(value)) : null;
|
|
2670
2971
|
if (!option) return lit__WEBPACK_IMPORTED_MODULE_0__.nothing;
|
|
2671
|
-
const badge = (0,_helpers_option_badge_js__WEBPACK_IMPORTED_MODULE_2__.optionBadgeProps)(option);
|
|
2972
|
+
const badge = (0,_helpers_option_badge_js__WEBPACK_IMPORTED_MODULE_2__.optionBadgeProps)(option); // A table cell is a dense place: the badge keeps the tight side room
|
|
2973
|
+
// (sf-badge tight), as it had before sf-badge 1.3.0 widened the default.
|
|
2974
|
+
|
|
2672
2975
|
return (0,lit__WEBPACK_IMPORTED_MODULE_0__.html)`
|
|
2673
2976
|
<sf-badge
|
|
2674
2977
|
line=${VIEW_LINE}
|
|
2978
|
+
tight
|
|
2675
2979
|
appearance=${field.badgeAppearance ?? CELL_BADGE_APPEARANCE}
|
|
2676
2980
|
scheme=${badge.scheme}
|
|
2677
2981
|
style=${badge.rootStyle || lit__WEBPACK_IMPORTED_MODULE_0__.nothing}
|
|
@@ -2997,12 +3301,13 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2997
3301
|
/* harmony export */ modesOf: () => (/* binding */ modesOf)
|
|
2998
3302
|
/* harmony export */ });
|
|
2999
3303
|
// The registry of universal properties (owner, 2026-10-15): every property
|
|
3000
|
-
// type the Framework renders,
|
|
3001
|
-
//
|
|
3002
|
-
//
|
|
3003
|
-
// from the renderers' source. Which
|
|
3004
|
-
// is
|
|
3005
|
-
//
|
|
3304
|
+
// type the Framework renders, or a component set renders beside it (`set`),
|
|
3305
|
+
// its variants and the parameters a field of it reads -- one list a host
|
|
3306
|
+
// (Larena's fields and settings), the property workbench and the documentation
|
|
3307
|
+
// read, instead of each learning the types from the renderers' source. Which
|
|
3308
|
+
// modes a type has is not written here: it is read from the renderer maps
|
|
3309
|
+
// themselves (modesOf), so the registry cannot claim a mode the set does not
|
|
3310
|
+
// draw. A contract keeps the two the same.
|
|
3006
3311
|
//
|
|
3007
3312
|
// `params` are the field keys the type's renderers read -- each one, and no
|
|
3008
3313
|
// other (the probe field-renderer-params calls them with a field that records
|
|
@@ -3257,6 +3562,58 @@ const PROPERTY_TYPES = Object.freeze([{
|
|
|
3257
3562
|
},
|
|
3258
3563
|
variants: [],
|
|
3259
3564
|
params: ['language', 'required', 'readonly']
|
|
3565
|
+
}, // The types a component set draws (property-view port 1.18.0; Larena
|
|
3566
|
+
// property types standard, notes of 2026-11-03): `set` names the set whose
|
|
3567
|
+
// components show and edit them -- the View set's sf-view-properties and
|
|
3568
|
+
// sf-view-flow -- and the Framework draws their table cells and filters
|
|
3569
|
+
// (money and percent as numbers, items by its number of rows). `params` are
|
|
3570
|
+
// the keys the type reads, in the set's renderers and in these. Every
|
|
3571
|
+
// number is a decimal string, never a float.
|
|
3572
|
+
{
|
|
3573
|
+
type: 'money',
|
|
3574
|
+
label: {
|
|
3575
|
+
ru: 'Деньги',
|
|
3576
|
+
en: 'Money'
|
|
3577
|
+
},
|
|
3578
|
+
set: 'view',
|
|
3579
|
+
variants: [],
|
|
3580
|
+
params: ['currency', 'min', 'max', 'required', 'readonly']
|
|
3581
|
+
}, {
|
|
3582
|
+
type: 'percent',
|
|
3583
|
+
label: {
|
|
3584
|
+
ru: 'Процент',
|
|
3585
|
+
en: 'Percent'
|
|
3586
|
+
},
|
|
3587
|
+
set: 'view',
|
|
3588
|
+
variants: [],
|
|
3589
|
+
params: ['min', 'max', 'required', 'readonly']
|
|
3590
|
+
}, {
|
|
3591
|
+
type: 'items',
|
|
3592
|
+
label: {
|
|
3593
|
+
ru: 'Строки',
|
|
3594
|
+
en: 'Rows'
|
|
3595
|
+
},
|
|
3596
|
+
set: 'view',
|
|
3597
|
+
variants: [],
|
|
3598
|
+
params: ['columns', 'minRows', 'maxRows', 'required', 'readonly']
|
|
3599
|
+
}, {
|
|
3600
|
+
type: 'compound',
|
|
3601
|
+
label: {
|
|
3602
|
+
ru: 'Составное',
|
|
3603
|
+
en: 'Compound'
|
|
3604
|
+
},
|
|
3605
|
+
set: 'view',
|
|
3606
|
+
variants: [],
|
|
3607
|
+
params: ['parts', 'required', 'readonly']
|
|
3608
|
+
}, {
|
|
3609
|
+
type: 'ru.requisites',
|
|
3610
|
+
label: {
|
|
3611
|
+
ru: 'Реквизиты',
|
|
3612
|
+
en: 'Requisites (Russia)'
|
|
3613
|
+
},
|
|
3614
|
+
set: 'view',
|
|
3615
|
+
variants: [],
|
|
3616
|
+
params: ['parts', 'required', 'readonly']
|
|
3260
3617
|
}]); // The modes a type has, read from the renderer maps: view and edit (a record),
|
|
3261
3618
|
// cell (a table) and filter -- `own` when the set has a filter control for the
|
|
3262
3619
|
// type, `text` when a filter falls back to a text search.
|
|
Binary file
|