diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1
index d62215ef..e4148cd1 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.80 — Compile 1C managed form from JSON or object metadata
+# form-compile v1.81 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -2287,6 +2287,34 @@ function Emit-Events {
X "$indent"
}
+# ExtendedTooltip — это LabelDecoration: может нести own-content (layout/оформление/флаги/hyperlink)
+# вместо/вместе с текстом. Признак структурированной формы: объект с любым НЕ-текстовым ключом
+# ({text,formatted} и языковые ключи {ru,en} → обычная текст-форма).
+$script:companionStructKeys = @(
+ 'width','autoMaxWidth','maxWidth','height','autoMaxHeight','maxHeight','verticalAlign','titleHeight',
+ 'horizontalStretch','verticalStretch','horizontalAlign','groupHorizontalAlign','groupVerticalAlign',
+ 'visible','hidden','enabled','disabled','hyperlink',
+ 'textColor','backColor','borderColor','font','border','цветтекста','цветфона','цветрамки','шрифт','рамка'
+)
+function Test-CompanionStructured {
+ param($content)
+ if (-not (($content -is [System.Collections.IDictionary]) -or ($content -is [System.Management.Automation.PSCustomObject]))) { return $false }
+ foreach ($k in $script:companionStructKeys) {
+ $present = if ($content -is [System.Collections.IDictionary]) { $content.Contains($k) } else { [bool]$content.PSObject.Properties[$k] }
+ if ($present) { return $true }
+ }
+ return $false
+}
+
+function Emit-CompanionTitle {
+ param($content, [string]$indent)
+ $r = Resolve-MLFormatted $content
+ $fmt = if ($r.formatted) { 'true' } else { 'false' }
+ X "$indent
"
+ Emit-MLItems -val $r.text -indent "$indent`t"
+ X "$indent"
+}
+
function Emit-Companion {
param([string]$tag, [string]$name, [string]$indent, $content = $null)
$id = New-Id
@@ -2295,13 +2323,20 @@ function Emit-Companion {
X "$indent<$tag name=`"$name`" id=`"$id`"/>"
return
}
- # Companion с контентом: (расширенная подсказка)
+ $inner = "$indent`t"
X "$indent<$tag name=`"$name`" id=`"$id`">"
- $r = Resolve-MLFormatted $content
- $fmt = if ($r.formatted) { 'true' } else { 'false' }
- X "$indent`t"
- Emit-MLItems -val $r.text -indent "$indent`t`t"
- X "$indent`t"
+ if (Test-CompanionStructured $content) {
+ # структурированная форма (own-content). Порядок как у платформы: own-content (флаги/hyperlink/
+ # layout/оформление) ПЕРЕД Title (в корпусе layout-first 582 vs 10).
+ $txtPresent = if ($content -is [System.Collections.IDictionary]) { $content.Contains('text') } else { [bool]$content.PSObject.Properties['text'] }
+ Emit-CommonFlags -el $content -indent $inner
+ if ($content.hyperlink -eq $true) { X "$innertrue" }
+ Emit-Layout -el $content -indent $inner
+ Emit-Appearance -el $content -indent $inner -profile 'decoration'
+ if ($txtPresent) { Emit-CompanionTitle -content $content -indent $inner }
+ } else {
+ Emit-CompanionTitle -content $content -indent $inner
+ }
X "$indent$tag>"
}
diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py
index a20bf72d..e3d44ccc 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.80 — Compile 1C managed form from JSON or object metadata
+# form-compile v1.81 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import copy
@@ -2296,17 +2296,42 @@ def resolve_ml_formatted(val):
return val, _has_real_markup(val)
+# ExtendedTooltip — это LabelDecoration: own-content (layout/оформление/флаги/hyperlink) ±текст.
+# Признак структурированной формы: объект с любым НЕ-текстовым ключом ({text,formatted}/{ru,en} → текст).
+COMPANION_STRUCT_KEYS = {
+ 'width', 'autoMaxWidth', 'maxWidth', 'height', 'autoMaxHeight', 'maxHeight', 'verticalAlign', 'titleHeight',
+ 'horizontalStretch', 'verticalStretch', 'horizontalAlign', 'groupHorizontalAlign', 'groupVerticalAlign',
+ 'visible', 'hidden', 'enabled', 'disabled', 'hyperlink',
+ 'textColor', 'backColor', 'borderColor', 'font', 'border', 'цветтекста', 'цветфона', 'цветрамки', 'шрифт', 'рамка',
+}
+
+
+def emit_companion_title(lines, content, indent):
+ text, fmt = resolve_ml_formatted(content)
+ lines.append(f'{indent}')
+ emit_ml_items(lines, f'{indent}\t', text)
+ lines.append(f'{indent}')
+
+
def emit_companion(lines, tag, name, indent, content=None):
cid = new_id()
has_content = content is not None and not (isinstance(content, str) and content == '')
if not has_content:
lines.append(f'{indent}<{tag} name="{name}" id="{cid}"/>')
return
+ inner = f'{indent}\t'
lines.append(f'{indent}<{tag} name="{name}" id="{cid}">')
- text, fmt = resolve_ml_formatted(content)
- lines.append(f'{indent}\t')
- emit_ml_items(lines, f'{indent}\t\t', text)
- lines.append(f'{indent}\t')
+ if isinstance(content, dict) and any(k in content for k in COMPANION_STRUCT_KEYS):
+ # own-content ПЕРЕД Title (в корпусе layout-first 582 vs 10).
+ emit_common_flags(lines, content, inner)
+ if content.get('hyperlink') is True:
+ lines.append(f'{inner}true')
+ emit_layout(lines, content, inner)
+ emit_appearance(lines, content, inner, 'decoration')
+ if 'text' in content:
+ emit_companion_title(lines, content, inner)
+ else:
+ emit_companion_title(lines, content, inner)
lines.append(f'{indent}{tag}>')
diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1
index 0a1ec0e2..5ac7ce4a 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.56 — Decompile 1C managed Form.xml to JSON DSL (draft)
+# form-decompile v0.57 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -1599,9 +1599,30 @@ function Decompile-Element {
}
Add-Layout $obj $node
Add-GenericScalars $obj $node
- # extendedTooltip: контент companion (любой элемент)
- $etTitle = $node.SelectSingleNode("lf:ExtendedTooltip/lf:Title", $ns)
- if ($etTitle) { $et = Get-MLFormattedValue $etTitle; if ($null -ne $et) { $obj['extendedTooltip'] = $et } }
+ # extendedTooltip: companion (это LabelDecoration). Текст-форма (только ) →
+ # строка/{text,formatted}. Own-content (layout/оформление/флаги/hyperlink) → объект { text?, … }.
+ # Events компаньона пока НЕ захватываем (хвост — требует имя-обработчик).
+ $etNode = $node.SelectSingleNode("lf:ExtendedTooltip", $ns)
+ if ($etNode) {
+ $etTitle = $etNode.SelectSingleNode("lf:Title", $ns)
+ $textVal = if ($etTitle) { Get-MLFormattedValue $etTitle } else { $null }
+ $etObj = [ordered]@{}
+ Add-Layout $etObj $etNode
+ Add-GenericScalars $etObj $etNode
+ Add-Appearance $etObj $etNode
+ if ((Get-Child $etNode 'Visible') -eq 'false') { $etObj['hidden'] = $true }
+ if ((Get-Child $etNode 'Enabled') -eq 'false') { $etObj['disabled'] = $true }
+ if ((Get-Child $etNode 'Hyperlink') -eq 'true') { $etObj['hyperlink'] = $true }
+ if ($etObj.Count -gt 0) {
+ if ($null -ne $textVal) {
+ if ($textVal -is [System.Collections.IDictionary]) { $etObj['text'] = $textVal['text']; if ($textVal['formatted']) { $etObj['formatted'] = $true } }
+ else { $etObj['text'] = $textVal }
+ }
+ $obj['extendedTooltip'] = $etObj
+ } elseif ($null -ne $textVal) {
+ $obj['extendedTooltip'] = $textVal
+ }
+ }
# companion-панели с контентом: AutoCommandBar → commandBar, ContextMenu → contextMenu (любой элемент)
$isDynListTable = ($tag -eq 'Table') -and (Has-Child $node 'UpdateOnDataChange')
$cb = Decompile-CompanionPanel $node 'AutoCommandBar' $isDynListTable
diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md
index 34425e99..7defcfa0 100644
--- a/docs/form-dsl-spec.md
+++ b/docs/form-dsl-spec.md
@@ -121,7 +121,7 @@
| `titleLocation` | string | Расположение заголовка: `none`/`left`/`right`/`top`/`bottom`/`auto`. Эмитится при наличии (input, labelField, picField, table, calendar). У `check`/`radio` — особая семантика с умным дефолтом (см. их разделы) |
| `tooltip` | string/object | Всплывающая подсказка элемента (``). Строка → ru, объект `{ "ru": …, "en": … }` → мультиязычный (как `title`). Эмитится сразу после `title` |
| `tooltipRepresentation` | string | Режим показа подсказки (``): `None`, `Button`, `ShowBottom`, `ShowTop`, `ShowLeft`, `ShowRight`, `ShowAuto`, `Balloon`. Эмитится при наличии |
-| `extendedTooltip` | string/object | Расширенная подсказка (контент companion ``). См. форму ML-текста ниже. Синоним: `extTooltip` |
+| `extendedTooltip` | string/object | Расширенная подсказка (companion ``, по сути LabelDecoration). **Текст-форма**: строка / ML / `{text, formatted}`. **Own-content форма** (объект с layout/оформлением/флагами): `{ text?, formatted?, width?, autoMaxWidth?, maxWidth?, height?, horizontalStretch?, verticalAlign?, titleHeight?, hyperlink?, visible?, enabled?, textColor?, font?, … }` — own-content эмитится перед `Title`. (События компаньона пока не поддержаны.) Синоним: `extTooltip` |
#### Форма ML-текста и `formatted`
diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json
index 08bd8735..bfe26a96 100644
--- a/tests/skills/cases/form-compile/input-fields.json
+++ b/tests/skills/cases/form-compile/input-fields.json
@@ -16,7 +16,7 @@
"input": {
"title": "Поля ввода",
"elements": [
- { "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле", "tooltip": "Введите значение поля", "tooltipRepresentation": "ShowBottom", "editMode": "EnterOnInput", "horizontalStretch": false, "verticalStretch": false, "format": "ЧДЦ=2", "editFormat": "ЧДЦ=2; ЧН=" },
+ { "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле", "tooltip": "Введите значение поля", "tooltipRepresentation": "ShowBottom", "editMode": "EnterOnInput", "horizontalStretch": false, "verticalStretch": false, "format": "ЧДЦ=2", "editFormat": "ЧДЦ=2; ЧН=", "extendedTooltip": { "width": 30, "autoMaxWidth": false, "text": "Расширенная подсказка поля" } },
{ "labelField": "Ссылка", "path": "ОбычноеПоле", "titleLocation": "left", "hyperlink": true, "format": { "ru": "ДЛФ=D", "en": "DLF=D" } },
{ "input": "МногострочноеПоле", "path": "МногострочноеПоле", "multiLine": true, "height": 5, "title": "Комментарий", "wrap": false, "showInHeader": false, "showInFooter": false, "autoCellHeight": true, "footerHorizontalAlign": "Right", "openButton": false, "chooseType": false },
{ "input": "ПолеПароля", "path": "ПолеПароля", "passwordMode": true, "title": "Пароль" },
@@ -45,7 +45,7 @@
"choiceParameterLinks": [ "Отбор.Организация=ОбычноеПоле", "Отбор.Тип=ПолеСписокВыбора:DontChange" ],
"typeLink": "ПолеПодсказка"
},
- { "check": "Флаг", "path": "Флаг", "title": "Включено" },
+ { "check": "Флаг", "path": "Флаг", "title": "Включено", "extendedTooltip": { "width": 45 } },
{ "check": "ФлагПлатформенный", "path": "ФлагПлатформенный", "title": "Слева", "titleLocation": "" },
{ "check": "ФлагЯвный", "path": "ФлагЯвный", "title": "Сверху", "titleLocation": "top" },
{ "check": "ФлагТумблер", "path": "ФлагТумблер", "title": "Тумблер", "checkBoxType": "switcher", "format": "БЛ=Нет; БИ=Да" }
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 3b8dbff8..54bac057 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
@@ -40,7 +40,16 @@
-
+
+ false
+ 30
+
+
+ ru
+ Расширенная подсказка поля
+
+
+
ОбычноеПоле
@@ -308,7 +317,9 @@
Auto
Right
-
+
+ 45
+
ФлагПлатформенный