diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 0bc214af..de2bb0f1 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.98 — Compile 1C managed form from JSON or object metadata +# form-compile v1.99 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -2618,6 +2618,9 @@ function Emit-Element { "horizontalSpacing"=1;"representationInContextMenu"=1;"settingsNamedItemDetailedRepresentation"=1 # хвост: высота элемента списка / ширина выпадающего списка / картинка кнопки выбора / прозрачный пиксель "itemHeight"=1;"dropListWidth"=1;"choiceButtonPicture"=1;"transparentPixel"=1 + # хвост CI-форм: динамический заголовок / расширенное редактирование / высота таблицы + "titleDataPath"=1;"extendedEdit"=1;"maxRowsCount"=1;"autoMaxRowsCount"=1;"heightControlVariant"=1 + "warningOnEdit"=1;"nonselectedPictureText"=1;"editTextUpdate"=1;"footerText"=1 # columnGroup-specific "showInHeader"=1 # radio-specific @@ -2925,6 +2928,13 @@ $script:genericScalars = @( # Хвост: высота элемента списка (radio) / ширина выпадающего списка (input) @{ Tag='ItemHeight'; Key='itemHeight'; Kind='value' } @{ Tag='DropListWidth'; Key='dropListWidth'; Kind='value' } + # Хвост CI-форм: динамический заголовок (Page/Group) / расширенное ред. (input) / высота таблицы по строкам + @{ Tag='TitleDataPath'; Key='titleDataPath'; Kind='value' } + @{ Tag='ExtendedEdit'; Key='extendedEdit'; Kind='bool' } + @{ Tag='MaxRowsCount'; Key='maxRowsCount'; Kind='value' } + @{ Tag='AutoMaxRowsCount'; Key='autoMaxRowsCount'; Kind='bool' } + @{ Tag='HeightControlVariant'; Key='heightControlVariant'; Kind='value' } + @{ Tag='EditTextUpdate'; Key='editTextUpdate'; Kind='value' } ) function Emit-GenericScalars { @@ -3237,7 +3247,7 @@ function Emit-Input { if ($null -ne $el.spinButton) { X "$inner$(if ($el.spinButton){'true'}else{'false'})" } if ($null -ne $el.dropListButton) { X "$inner$(if ($el.dropListButton){'true'}else{'false'})" } if ($null -ne $el.choiceListButton) { X "$inner$(if ($el.choiceListButton){'true'}else{'false'})" } - if ($el.markIncomplete -eq $true) { X "$innertrue" } + if ($null -ne $el.markIncomplete) { X "$inner$(if ($el.markIncomplete){'true'}else{'false'})" } if ($el.editMode) { X "$inner$($el.editMode)" } Emit-ColumnPics -el $el -indent $inner if ($el.textEdit -eq $false) { X "$innerfalse" } @@ -3270,6 +3280,8 @@ function Emit-Input { if ($el.inputHint) { Emit-MLText -tag "InputHint" -text $el.inputHint -indent $inner } + if ($null -ne $el.warningOnEdit) { Emit-MLText -tag "WarningOnEdit" -text $el.warningOnEdit -indent $inner } + if ($null -ne $el.footerText) { Emit-MLText -tag "FooterText" -text $el.footerText -indent $inner } # Формат / формат редактирования (LocalStringType — строка или {ru,en}) if ($el.format) { Emit-MLText -tag "Format" -text $el.format -indent $inner } @@ -3893,7 +3905,7 @@ function Emit-Table { if ($null -ne $el.autofill) { X "$inner$(if ($el.autofill){'true'}else{'false'})" } if ($el.multipleChoice -eq $true) { X "$innertrue" } if ($el.searchOnInput) { X "$inner$($el.searchOnInput)" } - if ($el.markIncomplete -eq $true) { X "$innertrue" } + if ($null -ne $el.markIncomplete) { X "$inner$(if ($el.markIncomplete){'true'}else{'false'})" } if ($el.useAlternationRowColor -eq $true) { X "$innertrue" } if ($el.selectionMode) { X "$inner$($el.selectionMode)" } if ($el.rowSelectionMode) { X "$inner$($el.rowSelectionMode)" } @@ -4187,6 +4199,7 @@ function Emit-PictureField { # Required for a Boolean-bound PictureField to actually show an icon. # Скаляр (Ref) или объект {src, loadTransparent}; LoadTransparent эмитится всегда. Emit-PictureRef -val $el.valuesPicture -picTag 'ValuesPicture' -indent $inner + if ($null -ne $el.nonselectedPictureText) { Emit-MLText -tag "NonselectedPictureText" -text $el.nonselectedPictureText -indent $inner } # Оформление (цвета/шрифты/граница) — перед компаньонами Emit-Appearance -el $el -indent $inner -profile 'field' diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 77cd1855..14bb19df 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.98 — Compile 1C managed form from JSON or object metadata +# form-compile v1.99 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -1839,6 +1839,9 @@ KNOWN_KEYS = { "horizontalSpacing", "representationInContextMenu", "settingsNamedItemDetailedRepresentation", # хвост: высота элемента списка / ширина выпадающего списка / картинка кнопки выбора / прозрачный пиксель "itemHeight", "dropListWidth", "choiceButtonPicture", "transparentPixel", + # хвост CI-форм: динамический заголовок / расширенное редактирование / высота таблицы + "titleDataPath", "extendedEdit", "maxRowsCount", "autoMaxRowsCount", "heightControlVariant", + "warningOnEdit", "nonselectedPictureText", "editTextUpdate", "footerText", } # picture/picField — НИЗКИЙ приоритет: 'picture' это и тип (PictureDecoration), и свойство-иконка @@ -2752,6 +2755,13 @@ GENERIC_SCALARS = [ # Хвост: высота элемента списка (radio) / ширина выпадающего списка (input) ('ItemHeight', 'itemHeight', 'value'), ('DropListWidth', 'dropListWidth', 'value'), + # Хвост CI-форм: динамический заголовок (Page/Group) / расширенное ред. (input) / высота таблицы по строкам + ('TitleDataPath', 'titleDataPath', 'value'), + ('ExtendedEdit', 'extendedEdit', 'bool'), + ('MaxRowsCount', 'maxRowsCount', 'value'), + ('AutoMaxRowsCount', 'autoMaxRowsCount', 'bool'), + ('HeightControlVariant', 'heightControlVariant', 'value'), + ('EditTextUpdate', 'editTextUpdate', 'value'), ] @@ -3299,8 +3309,8 @@ def emit_input(lines, el, name, eid, indent): lines.append(f'{inner}{"true" if el["dropListButton"] else "false"}') if el.get('choiceListButton') is not None: lines.append(f'{inner}{"true" if el["choiceListButton"] else "false"}') - if el.get('markIncomplete') is True: - lines.append(f'{inner}true') + if el.get('markIncomplete') is not None: + lines.append(f'{inner}{"true" if el["markIncomplete"] else "false"}') if el.get('editMode'): lines.append(f'{inner}{el["editMode"]}') emit_column_pics(lines, el, inner) @@ -3329,6 +3339,10 @@ def emit_input(lines, el, name, eid, indent): if el.get('inputHint'): emit_mltext(lines, inner, 'InputHint', el['inputHint']) + if el.get('warningOnEdit') is not None: + emit_mltext(lines, inner, 'WarningOnEdit', el['warningOnEdit']) + if el.get('footerText') is not None: + emit_mltext(lines, inner, 'FooterText', el['footerText']) # Формат / формат редактирования (LocalStringType — строка или {ru,en}) if el.get('format'): @@ -3591,8 +3605,8 @@ def emit_table(lines, el, name, eid, indent): lines.append(f'{inner}true') if el.get('searchOnInput'): lines.append(f'{inner}{el["searchOnInput"]}') - if el.get('markIncomplete') is True: - lines.append(f'{inner}true') + if el.get('markIncomplete') is not None: + lines.append(f'{inner}{"true" if el["markIncomplete"] else "false"}') if el.get('useAlternationRowColor') is True: lines.append(f'{inner}true') if el.get('selectionMode'): @@ -3881,6 +3895,8 @@ def emit_picture_field(lines, el, name, eid, indent): # Required for a Boolean-bound PictureField to actually show an icon. # Скаляр (Ref) или объект {src, loadTransparent}; LoadTransparent эмитится всегда. emit_picture_ref(lines, el.get('valuesPicture'), 'ValuesPicture', inner) + if el.get('nonselectedPictureText') is not None: + emit_mltext(lines, inner, 'NonselectedPictureText', el['nonselectedPictureText']) # Оформление (цвета/шрифты/граница) — перед компаньонами emit_appearance(lines, el, inner, 'field') diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 681aec79..9f3db16d 100644 --- a/.claude/skills/form-decompile/scripts/form-decompile.ps1 +++ b/.claude/skills/form-decompile/scripts/form-decompile.ps1 @@ -1,4 +1,4 @@ -# form-decompile v0.74 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.75 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -1226,6 +1226,13 @@ $GENERIC_SCALARS = @( # Хвост: высота элемента списка (radio) / ширина выпадающего списка (input) @{ Tag='ItemHeight'; Key='itemHeight'; Kind='value' } @{ Tag='DropListWidth'; Key='dropListWidth'; Kind='value' } + # Хвост CI-форм: динамический заголовок (Page/Group) / расширенное ред. (input) / высота таблицы по строкам + @{ Tag='TitleDataPath'; Key='titleDataPath'; Kind='value' } + @{ Tag='ExtendedEdit'; Key='extendedEdit'; Kind='bool' } + @{ Tag='MaxRowsCount'; Key='maxRowsCount'; Kind='value' } + @{ Tag='AutoMaxRowsCount'; Key='autoMaxRowsCount'; Kind='bool' } + @{ Tag='HeightControlVariant'; Key='heightControlVariant'; Kind='value' } + @{ Tag='EditTextUpdate'; Key='editTextUpdate'; Kind='value' } ) # Захват generic-скаляров. Специфичная обработка (если ключ уже задан) — побеждает. @@ -1485,10 +1492,12 @@ function Decompile-Element { Add-CommonProps $obj $node $name if ((Get-Child $node 'MultiLine') -eq 'true') { $obj['multiLine'] = $true } if ((Get-Child $node 'PasswordMode') -eq 'true') { $obj['passwordMode'] = $true } - if ((Get-Child $node 'AutoMarkIncomplete') -eq 'true') { $obj['markIncomplete'] = $true } + $mi = Get-Child $node 'AutoMarkIncomplete'; if ($null -ne $mi) { $obj['markIncomplete'] = ($mi -eq 'true') } $em = Get-Child $node 'EditMode'; if ($em) { $obj['editMode'] = $em } $tl = Get-Child $node 'TitleLocation'; if ($tl) { $obj['titleLocation'] = $tl.ToLower() } $ih = $node.SelectSingleNode("lf:InputHint", $ns); if ($ih) { $t = Get-LangText $ih; if ($t) { $obj['inputHint'] = $t } } + $woe = $node.SelectSingleNode("lf:WarningOnEdit", $ns); if ($woe) { $t = Get-LangText $woe; if ($null -ne $t) { $obj['warningOnEdit'] = $t } } + $ftxt = $node.SelectSingleNode("lf:FooterText", $ns); if ($ftxt) { $t = Get-LangText $ftxt; if ($null -ne $t) { $obj['footerText'] = $t } } foreach ($p in @('ChoiceButton','ClearButton','SpinButton','DropListButton','ChoiceListButton')) { $v = Get-Child $node $p; if ($null -ne $v) { $obj[($p.Substring(0,1).ToLower()+$p.Substring(1))] = (To-Bool $v) } } @@ -1587,6 +1596,7 @@ function Decompile-Element { if ((Get-Child $node 'Hyperlink') -eq 'true') { $obj['hyperlink'] = $true } $sct = Get-Child $node 'Shortcut'; if ($sct) { $obj['shortcut'] = $sct } $vp = Get-PictureRef $node 'ValuesPicture'; if ($null -ne $vp) { $obj['valuesPicture'] = $vp } + $npt = $node.SelectSingleNode("lf:NonselectedPictureText", $ns); if ($npt) { $t = Get-LangText $npt; if ($null -ne $t) { $obj['nonselectedPictureText'] = $t } } } 'CalendarField' { $obj[$key] = $name @@ -1635,7 +1645,7 @@ function Decompile-Element { $taf = Get-Child $node 'Autofill'; if ($null -ne $taf) { $obj['autofill'] = ($taf -eq 'true') } if ((Get-Child $node 'MultipleChoice') -eq 'true') { $obj['multipleChoice'] = $true } $soin = Get-Child $node 'SearchOnInput'; if ($soin) { $obj['searchOnInput'] = $soin } - if ((Get-Child $node 'AutoMarkIncomplete') -eq 'true') { $obj['markIncomplete'] = $true } + $mi = Get-Child $node 'AutoMarkIncomplete'; if ($null -ne $mi) { $obj['markIncomplete'] = ($mi -eq 'true') } $itv = Get-Child $node 'InitialTreeView'; if ($itv) { $obj['initialTreeView'] = $itv } # RowsPicture: src (xr:Ref) + LoadTransparent (дефолт false). При true → объектная форма. $rpRef = $node.SelectSingleNode("lf:RowsPicture/xr:Ref", $ns) diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index c48ab98d..020c568f 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -120,6 +120,7 @@ |----------|-----|----------| | `name` | string | Имя элемента (по умолчанию — из значения ключа типа) | | `title` | string/object | Заголовок. **Нет ключа** → авто-вывод из имени (для page/popup/label и непривязанных полей/кнопок). **`""`** → подавить (заголовок не выводится). Строка → ru. Объект `{ "ru": "…", "en": "…" }` → мультиязычный (по `` на язык). Так же `tooltip`/`inputHint`/`title` команд/реквизитов/колонок | +| `titleDataPath` | string | Путь данных динамического заголовка (``) — у Page/UsualGroup (напр. `Объект.Товары.RowsCount` в заголовке страницы). Парный к `footerDataPath` (путь данных подвала поля) | | `hidden` | bool | `true` → `false` | | `disabled` | bool | `true` → `false` | | `readOnly` | bool | `true` → `true` | @@ -375,7 +376,11 @@ companion-панели с собственным контентом. Оба не | `clearButton` | bool | Показывать кнопку очистки | | `spinButton` | bool | Показывать кнопку прокрутки | | `dropListButton` | bool | Показывать кнопку раскрытия | -| `markIncomplete` | bool | Автопометка незаполненных | +| `markIncomplete` | bool | Автопометка незаполненных (``, факт. значение true/false) | +| `extendedEdit` | bool | Расширенное редактирование (``) | +| `editTextUpdate` | string | Обновление текста при редактировании: `Always`, `OnValueChange`, `DontUse` | +| `warningOnEdit` | string/object | Предупреждение при редактировании (``, мультиязычный текст) | +| `footerText` | string/object | Текст подвала поля (``, мультиязычный) | | `editMode` | string | Режим редактирования: `EnterOnInput`, `Directly` | | `skipOnInput` | bool | Пропускать при вводе | | `inputHint` | string | Подсказка ввода (placeholder) | diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json index 24ff65cc..9c574529 100644 --- a/tests/skills/cases/form-compile/input-fields.json +++ b/tests/skills/cases/form-compile/input-fields.json @@ -27,7 +27,7 @@ { "value": "Первый" }, { "value": "Второй", "presentation": "Второй вариант" } ]}, - { "input": "ПолеПодсказка", "path": "ПолеПодсказка", "inputHint": "Введите значение...", "title": "Подсказка" }, + { "input": "ПолеПодсказка", "path": "ПолеПодсказка", "inputHint": "Введите значение...", "warningOnEdit": "Изменение запрещено", "footerText": "итого", "extendedEdit": true, "editTextUpdate": "OnValueChange", "markIncomplete": false, "title": "Подсказка" }, { "input": "ПолеСвязи", "path": "ПолеСвязи", "title": "Поле со связями", "choiceParameters": [ { "name": "Отбор.Активный", "value": true }, diff --git a/tests/skills/cases/form-compile/pages.json b/tests/skills/cases/form-compile/pages.json index 61eae37a..6c079523 100644 --- a/tests/skills/cases/form-compile/pages.json +++ b/tests/skills/cases/form-compile/pages.json @@ -21,7 +21,7 @@ { "page": "Шаг1", "title": "", "showTitle": false, "children": [ { "input": "Параметр1", "path": "Параметр1" } ]}, - { "page": "Шаг2", "title": "Результат", "tooltip": "Шаг \"Результат\"", "group": "horizontal", "children": [ + { "page": "Шаг2", "title": "Результат", "titleDataPath": "Итог", "tooltip": "Шаг \"Результат\"", "group": "horizontal", "children": [ { "input": "Итог", "path": "Итог", "readOnly": true } ]} ]}, diff --git a/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml index 1ecb72d8..2daf4900 100644 --- a/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml @@ -194,12 +194,27 @@ Подсказка + false + true + OnValueChange ru Введите значение... + + + ru + Изменение запрещено + + + + + ru + итого + + diff --git a/tests/skills/cases/form-compile/snapshots/pages/DataProcessors/Мастер/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/pages/DataProcessors/Мастер/Forms/Форма/Ext/Form.xml index fb05c5e0..7168ac00 100644 --- a/tests/skills/cases/form-compile/snapshots/pages/DataProcessors/Мастер/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/pages/DataProcessors/Мастер/Forms/Форма/Ext/Form.xml @@ -44,6 +44,7 @@ Horizontal + Итог diff --git a/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml index 6498daab..152be30b 100644 --- a/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml @@ -22,6 +22,9 @@ None 80 false + 5 + false + UseHeightInTableRows Add Delete diff --git a/tests/skills/cases/form-compile/table.json b/tests/skills/cases/form-compile/table.json index 3b35b2cb..ba3d6939 100644 --- a/tests/skills/cases/form-compile/table.json +++ b/tests/skills/cases/form-compile/table.json @@ -16,7 +16,7 @@ "input": { "title": "Просмотр данных", "elements": [ - { "table": "Данные", "path": "Данные", "changeRowSet": true, "titleLocation": "top", "height": 80, "heightInTableRows": 5, "autofill": true, "multipleChoice": true, "searchOnInput": "Use", "markIncomplete": true, "settingsNamedItemDetailedRepresentation": false, + { "table": "Данные", "path": "Данные", "changeRowSet": true, "titleLocation": "top", "height": 80, "heightInTableRows": 5, "autofill": true, "multipleChoice": true, "searchOnInput": "Use", "markIncomplete": true, "settingsNamedItemDetailedRepresentation": false, "heightControlVariant": "UseHeightInTableRows", "maxRowsCount": 5, "autoMaxRowsCount": false, "viewStatusLocation": "None", "searchControlLocation": "None", "excludedCommands": ["Add", "Delete", "MoveUp", "MoveDown"], "commandBar": { "autofill": false, "children": [