@simai-studio/view 1.1.0 → 1.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.
Files changed (70) hide show
  1. package/README.md +5 -5
  2. package/dist/package-receipt.json +310 -69
  3. package/dist/set.json +35 -2
  4. package/dist/smart/rule.js +94 -0
  5. package/dist/smart/rule.js.gz +0 -0
  6. package/dist/smart/view/component.json +1 -1
  7. package/dist/smart/view/component.json.gz +0 -0
  8. package/dist/smart/view/js/view.js +1 -1
  9. package/dist/smart/view/js/view.js.gz +0 -0
  10. package/dist/smart/view/js/view.min.js +1 -1
  11. package/dist/smart/view/js/view.min.js.gz +0 -0
  12. package/dist/smart/view/smart.manifest.json +1 -1
  13. package/dist/smart/view/smart.manifest.json.gz +0 -0
  14. package/dist/smart/view/static/default/open-record.html +1 -1
  15. package/dist/smart/view/static/default/open-record.html.gz +0 -0
  16. package/dist/smart/view/static/default/reference.html +1 -1
  17. package/dist/smart/view/static/default/reference.html.gz +0 -0
  18. package/dist/smart/view-flow/component.json +612 -0
  19. package/dist/smart/view-flow/component.json.gz +0 -0
  20. package/dist/smart/view-flow/css/view-flow.css +203 -0
  21. package/dist/smart/view-flow/css/view-flow.css.gz +0 -0
  22. package/dist/smart/view-flow/css/view-flow.min.css +2 -0
  23. package/dist/smart/view-flow/css/view-flow.min.css.gz +0 -0
  24. package/dist/smart/view-flow/examples.json +452 -0
  25. package/dist/smart/view-flow/examples.json.gz +0 -0
  26. package/dist/smart/view-flow/js/view-flow.js +5858 -0
  27. package/dist/smart/view-flow/js/view-flow.js.gz +0 -0
  28. package/dist/smart/view-flow/js/view-flow.min.js +638 -0
  29. package/dist/smart/view-flow/js/view-flow.min.js.gz +0 -0
  30. package/dist/smart/view-flow/lang/en.json +24 -0
  31. package/dist/smart/view-flow/lang/en.json.gz +0 -0
  32. package/dist/smart/view-flow/lang/ru.json +24 -0
  33. package/dist/smart/view-flow/lang/ru.json.gz +0 -0
  34. package/dist/smart/view-flow/probe.json +149 -0
  35. package/dist/smart/view-flow/probe.json.gz +0 -0
  36. package/dist/smart/view-flow/smart.manifest.json +478 -0
  37. package/dist/smart/view-flow/smart.manifest.json.gz +0 -0
  38. package/dist/smart/view-flow/static/default/answered.html +57 -0
  39. package/dist/smart/view-flow/static/default/answered.html.gz +0 -0
  40. package/dist/smart/view-flow/static/default/ask.html +146 -0
  41. package/dist/smart/view-flow/static/default/ask.html.gz +0 -0
  42. package/dist/smart/view-flow/static/default/expired.html +16 -0
  43. package/dist/smart/view-flow/static/default/expired.html.gz +0 -0
  44. package/dist/smart/view-flow/static/default/groups.html +44 -0
  45. package/dist/smart/view-flow/static/default/groups.html.gz +0 -0
  46. package/dist/smart/view-flow/static/default/reference.html +72 -0
  47. package/dist/smart/view-flow/static/default/reference.html.gz +0 -0
  48. package/dist/smart/view-flow/static/default/untitled.html +41 -0
  49. package/dist/smart/view-flow/static/default/untitled.html.gz +0 -0
  50. package/dist/smart/view-flow/static/sheets.json +443 -0
  51. package/dist/smart/view-flow/static/sheets.json.gz +0 -0
  52. package/dist/smart/view-flow/template/default.css +180 -0
  53. package/dist/smart/view-flow/template/default.css.gz +0 -0
  54. package/dist/smart/view-flow/template/default.js +136 -0
  55. package/dist/smart/view-flow/template/default.js.gz +0 -0
  56. package/dist/smart/view-properties/component.json +1 -1
  57. package/dist/smart/view-properties/component.json.gz +0 -0
  58. package/dist/smart/view-properties/js/view-properties.js +59 -21
  59. package/dist/smart/view-properties/js/view-properties.js.gz +0 -0
  60. package/dist/smart/view-properties/js/view-properties.min.js +8 -6
  61. package/dist/smart/view-properties/js/view-properties.min.js.gz +0 -0
  62. package/dist/smart/view-properties/smart.manifest.json +6 -2
  63. package/dist/smart/view-properties/smart.manifest.json.gz +0 -0
  64. package/dist/smart/view-properties/template/default.js +1 -77
  65. package/dist/smart/view-properties/template/default.js.gz +0 -0
  66. package/dist/smart/view-table/js/view-table.js +2 -0
  67. package/dist/smart/view-table/js/view-table.js.gz +0 -0
  68. package/dist/smart/view-table/js/view-table.min.js +2 -0
  69. package/dist/smart/view-table/js/view-table.min.js.gz +0 -0
  70. package/package.json +1 -1
@@ -0,0 +1,203 @@
1
+ @layer sf.reset, sf.tokens, sf.base, sf.utilities, sf.components, sf.overrides, sf.states;
2
+ @layer sf.components {
3
+ /* sf-view-flow. The host is display: contents; .sf-view-flow
4
+ is the block the flow places, as wide as the flow. Every value is a
5
+ Framework token. The public variables (component.json css-variables) are
6
+ read here and never set, on the block or any ancestor (public-variables.v1):
7
+ --sf-view-flow-radius the block's corner
8
+ --sf-view-flow-inset its padding
9
+ --sf-view-flow-gap between its rows
10
+ --sf-view-flow-background-color its ground
11
+ --sf-view-flow-border-color its edge
12
+ The --sf-view-flow--* names are the internal layer they feed. */
13
+ .sf-view-flow {
14
+ --sf-view-flow--radius: var(--sf-view-flow-radius, var(--sf-radius-2));
15
+ --sf-view-flow--inset: var(--sf-view-flow-inset, var(--sf-space-1\/2) var(--sf-space-1));
16
+ --sf-view-flow--gap: var(--sf-view-flow-gap, var(--sf-space-1\/2));
17
+ --sf-view-flow--fill: var(--sf-view-flow-background-color, var(--sf-surface-0));
18
+ --sf-view-flow--edge: var(--sf-view-flow-border-color, var(--sf-outline-variant));
19
+ --sf-view-flow--label-size: var(--sf-label-medium--size);
20
+ --sf-view-flow--label-height: var(--sf-label-medium--height);
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: var(--sf-view-flow--gap);
24
+ box-sizing: border-box;
25
+ width: 100%;
26
+ inline-size: 100%;
27
+ min-width: 0;
28
+ min-inline-size: 0;
29
+ padding: var(--sf-view-flow--inset);
30
+ border: var(--sf-px) solid var(--sf-view-flow--edge);
31
+ border-radius: var(--sf-view-flow--radius);
32
+ background: var(--sf-view-flow--fill);
33
+ color: var(--sf-on-surface);
34
+ font-family: var(--sf-text--family);
35
+ font-size: var(--sf-text--size);
36
+ line-height: var(--sf-text--height);
37
+ }
38
+ .sf-view-flow--size-1-2 {
39
+ font-size: var(--sf-label-large--size);
40
+ line-height: var(--sf-label-large--height);
41
+ }
42
+ .sf-view-flow__heading {
43
+ margin: 0;
44
+ font-weight: var(--sf-font-weight-bold);
45
+ }
46
+ .sf-view-flow__description {
47
+ color: var(--sf-on-surface-variant);
48
+ font-size: var(--sf-label-large--size);
49
+ line-height: var(--sf-label-large--height);
50
+ }
51
+ .sf-view-flow__groups {
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: var(--sf-view-flow--gap);
55
+ min-width: 0;
56
+ min-inline-size: 0;
57
+ }
58
+ .sf-view-flow__group {
59
+ display: flex;
60
+ flex-direction: column;
61
+ gap: var(--sf-space-1\/3);
62
+ min-width: 0;
63
+ min-inline-size: 0;
64
+ }
65
+ .sf-view-flow__group-label {
66
+ margin: 0;
67
+ color: var(--sf-on-surface-variant);
68
+ font-size: var(--sf-view-flow--label-size);
69
+ line-height: var(--sf-view-flow--label-height);
70
+ font-weight: var(--sf-font-weight-medium);
71
+ }
72
+ /* A collapsible group's label is a plain button: the words and a chevron
73
+ that turns when the group is closed. */
74
+ .sf-view-flow__toggle {
75
+ align-self: flex-start;
76
+ padding: 0;
77
+ border: 0;
78
+ background: none;
79
+ font-family: inherit;
80
+ cursor: pointer;
81
+ }
82
+ .sf-view-flow__toggle sf-icon {
83
+ transition: -webkit-transform var(--sf-duration-normal);
84
+ transition: transform var(--sf-duration-normal);
85
+ transition: transform var(--sf-duration-normal), -webkit-transform var(--sf-duration-normal);
86
+ }
87
+ .sf-view-flow__group[data-collapsed] .sf-view-flow__toggle sf-icon {
88
+ -webkit-transform: rotate(-90deg);
89
+ transform: rotate(-90deg);
90
+ }
91
+ /* Two columns: a half-width field pairs up with the next, a full one takes
92
+ the row. */
93
+ .sf-view-flow__fields {
94
+ display: grid;
95
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
96
+ grid-gap: var(--sf-space-1\/2) var(--sf-space-1);
97
+ gap: var(--sf-space-1\/2) var(--sf-space-1);
98
+ }
99
+ .sf-view-flow__fields[hidden] {
100
+ display: none;
101
+ }
102
+ .sf-view-flow__field--full {
103
+ grid-column: 1/-1;
104
+ }
105
+ .sf-view-flow__label {
106
+ font-size: var(--sf-view-flow--label-size);
107
+ line-height: var(--sf-view-flow--label-height);
108
+ }
109
+ .sf-view-flow__value {
110
+ min-height: var(--sf-view-flow--label-height);
111
+ min-block-size: var(--sf-view-flow--label-height);
112
+ word-wrap: anywhere;
113
+ }
114
+ /* While the block takes an answer a value is one control, and it fills its
115
+ field: in a flex row a control keeps its intrinsic width, and a Smart
116
+ element is display: contents, so its painted root is a grandchild that
117
+ flex: 1 on the box's children never reaches (sf-view-properties found the
118
+ same for its full-width fields). As a block it fills. */
119
+ .sf-view-flow--editing .sf-view-flow__value {
120
+ display: block;
121
+ }
122
+ .sf-view-flow__error {
123
+ font-size: var(--sf-view-flow--label-size);
124
+ line-height: var(--sf-view-flow--label-height);
125
+ }
126
+ .sf-view-flow__footer {
127
+ display: flex;
128
+ flex-wrap: wrap;
129
+ align-items: center;
130
+ justify-content: space-between;
131
+ gap: var(--sf-space-1\/3);
132
+ }
133
+ .sf-view-flow__status {
134
+ display: inline-flex;
135
+ align-items: center;
136
+ gap: var(--sf-space-1\/4);
137
+ margin: 0;
138
+ color: var(--sf-on-surface-variant);
139
+ font-size: var(--sf-view-flow--label-size);
140
+ line-height: var(--sf-view-flow--label-height);
141
+ }
142
+ .sf-view-flow__status.focus-visible {
143
+ outline-offset: var(--sf-space-1\/4);
144
+ }
145
+ .sf-view-flow__status:focus-visible {
146
+ outline-offset: var(--sf-space-1\/4);
147
+ }
148
+ .sf-view-flow__status--error {
149
+ color: var(--sf-error);
150
+ }
151
+ .sf-view-flow--accepted .sf-view-flow__status {
152
+ color: var(--sf-on-success-container);
153
+ }
154
+ _:-ms-input-placeholder, html:not([dir="rtl"]) .sf-view-flow__actions {
155
+ margin-left: auto;
156
+ }
157
+ @supports not (margin-inline-start: initial) {
158
+ html:not([dir="rtl"]) .sf-view-flow__actions {
159
+ margin-left: auto;
160
+ }
161
+ }
162
+ .sf-view-flow__actions:where(:dir(ltr)) {
163
+ margin-left: auto;
164
+ }
165
+ _:-ms-input-placeholder, [dir="rtl"] .sf-view-flow__actions {
166
+ margin-right: auto;
167
+ }
168
+ @supports not (margin-inline-start: initial) {
169
+ [dir="rtl"] .sf-view-flow__actions {
170
+ margin-right: auto;
171
+ }
172
+ }
173
+ .sf-view-flow__actions:where(:dir(rtl)) {
174
+ margin-right: auto;
175
+ }
176
+ .sf-view-flow__actions {
177
+ display: flex;
178
+ gap: var(--sf-space-1\/3);
179
+ -webkit-margin-start: auto;
180
+ margin-inline-start: auto;
181
+ }
182
+ .sf-view-flow__actions[hidden] {
183
+ display: none;
184
+ }
185
+ /* A narrow screen: one column, and the answers share the width. A media
186
+ query and not a container query: containing the block's inline size would
187
+ make a block as wide as its content (in a message bubble) collapse. */
188
+ @media (max-width: 479px) {
189
+ .sf-view-flow__fields {
190
+ grid-template-columns: minmax(0, 1fr);
191
+ }
192
+ .sf-view-flow__field--half {
193
+ grid-column: 1/-1;
194
+ }
195
+ .sf-view-flow__actions {
196
+ display: grid;
197
+ grid-auto-columns: 1fr;
198
+ grid-auto-flow: column;
199
+ width: 100%;
200
+ inline-size: 100%;
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,2 @@
1
+ @layer sf.reset, sf.tokens, sf.base, sf.utilities, sf.components, sf.overrides, sf.states;
2
+ @layer sf.components{.sf-view-flow{--sf-view-flow--radius: var(--sf-view-flow-radius, var(--sf-radius-2));--sf-view-flow--inset: var(--sf-view-flow-inset, var(--sf-space-1\/2) var(--sf-space-1));--sf-view-flow--gap: var(--sf-view-flow-gap, var(--sf-space-1\/2));--sf-view-flow--fill: var(--sf-view-flow-background-color, var(--sf-surface-0));--sf-view-flow--edge: var(--sf-view-flow-border-color, var(--sf-outline-variant));--sf-view-flow--label-size: var(--sf-label-medium--size);--sf-view-flow--label-height: var(--sf-label-medium--height);display:flex;flex-direction:column;gap:var(--sf-view-flow--gap);box-sizing:border-box;width:100%;inline-size:100%;min-width:0;min-inline-size:0;padding:var(--sf-view-flow--inset);border:var(--sf-px) solid var(--sf-view-flow--edge);border-radius:var(--sf-view-flow--radius);background:var(--sf-view-flow--fill);color:var(--sf-on-surface);font-family:var(--sf-text--family);font-size:var(--sf-text--size);line-height:var(--sf-text--height)}.sf-view-flow--size-1-2{font-size:var(--sf-label-large--size);line-height:var(--sf-label-large--height)}.sf-view-flow__heading{margin:0;font-weight:var(--sf-font-weight-bold)}.sf-view-flow__description{color:var(--sf-on-surface-variant);font-size:var(--sf-label-large--size);line-height:var(--sf-label-large--height)}.sf-view-flow__groups{display:flex;flex-direction:column;gap:var(--sf-view-flow--gap);min-width:0;min-inline-size:0}.sf-view-flow__group{display:flex;flex-direction:column;gap:var(--sf-space-1\/3);min-width:0;min-inline-size:0}.sf-view-flow__group-label{margin:0;color:var(--sf-on-surface-variant);font-size:var(--sf-view-flow--label-size);line-height:var(--sf-view-flow--label-height);font-weight:var(--sf-font-weight-medium)}.sf-view-flow__toggle{align-self:flex-start;padding:0;border:0;background:none;font-family:inherit;cursor:pointer}.sf-view-flow__toggle sf-icon{transition:-webkit-transform var(--sf-duration-normal);transition:transform var(--sf-duration-normal);transition:transform var(--sf-duration-normal), -webkit-transform var(--sf-duration-normal)}.sf-view-flow__group[data-collapsed] .sf-view-flow__toggle sf-icon{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.sf-view-flow__fields{display:grid;grid-template-columns:minmax(0, 1fr) minmax(0, 1fr);grid-gap:var(--sf-space-1\/2) var(--sf-space-1);gap:var(--sf-space-1\/2) var(--sf-space-1)}.sf-view-flow__fields[hidden]{display:none}.sf-view-flow__field--full{grid-column:1/-1}.sf-view-flow__label{font-size:var(--sf-view-flow--label-size);line-height:var(--sf-view-flow--label-height)}.sf-view-flow__value{min-height:var(--sf-view-flow--label-height);min-block-size:var(--sf-view-flow--label-height);word-wrap:anywhere}.sf-view-flow--editing .sf-view-flow__value{display:block}.sf-view-flow__error{font-size:var(--sf-view-flow--label-size);line-height:var(--sf-view-flow--label-height)}.sf-view-flow__footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--sf-space-1\/3)}.sf-view-flow__status{display:inline-flex;align-items:center;gap:var(--sf-space-1\/4);margin:0;color:var(--sf-on-surface-variant);font-size:var(--sf-view-flow--label-size);line-height:var(--sf-view-flow--label-height)}.sf-view-flow__status.focus-visible{outline-offset:var(--sf-space-1\/4)}.sf-view-flow__status:focus-visible{outline-offset:var(--sf-space-1\/4)}.sf-view-flow__status--error{color:var(--sf-error)}.sf-view-flow--accepted .sf-view-flow__status{color:var(--sf-on-success-container)}_:-ms-input-placeholder, html:not([dir="rtl"]) .sf-view-flow__actions{margin-left:auto}@supports not (margin-inline-start: initial){html:not([dir="rtl"]) .sf-view-flow__actions{margin-left:auto}}.sf-view-flow__actions:where(:dir(ltr)){margin-left:auto}_:-ms-input-placeholder, [dir="rtl"] .sf-view-flow__actions{margin-right:auto}@supports not (margin-inline-start: initial){[dir="rtl"] .sf-view-flow__actions{margin-right:auto}}.sf-view-flow__actions:where(:dir(rtl)){margin-right:auto}.sf-view-flow__actions{display:flex;gap:var(--sf-space-1\/3);-webkit-margin-start:auto;margin-inline-start:auto}.sf-view-flow__actions[hidden]{display:none}@media(max-width: 479px){.sf-view-flow__fields{grid-template-columns:minmax(0, 1fr)}.sf-view-flow__field--half{grid-column:1/-1}.sf-view-flow__actions{display:grid;grid-auto-columns:1fr;grid-auto-flow:column;width:100%;inline-size:100%}}}
@@ -0,0 +1,452 @@
1
+ {
2
+ "examples": [
3
+ {
4
+ "name": "reference",
5
+ "title": "Данные записи",
6
+ "settings": {
7
+ "heading": "Заявка на отпуск № 17",
8
+ "fields": [
9
+ {
10
+ "key": "employee",
11
+ "label": "Сотрудник",
12
+ "propertyType": "string",
13
+ "width": "full",
14
+ "value": "Ирина Волкова"
15
+ },
16
+ {
17
+ "key": "from",
18
+ "label": "С",
19
+ "propertyType": "date",
20
+ "width": "half",
21
+ "value": "2026-12-21"
22
+ },
23
+ {
24
+ "key": "to",
25
+ "label": "По",
26
+ "propertyType": "date",
27
+ "width": "half",
28
+ "value": "2027-01-08"
29
+ },
30
+ {
31
+ "key": "days",
32
+ "label": "Дней",
33
+ "propertyType": "integer",
34
+ "width": "half",
35
+ "value": 19
36
+ },
37
+ {
38
+ "key": "status",
39
+ "label": "Статус",
40
+ "propertyType": "choice",
41
+ "width": "half",
42
+ "value": "approved",
43
+ "options": [
44
+ {
45
+ "value": "approved",
46
+ "label": "Согласована",
47
+ "scheme": "success"
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ },
53
+ "expect": {
54
+ "name": "Заявка на отпуск № 17"
55
+ },
56
+ "parent": "<div style=\"inline-size: 32rem; max-inline-size: 100%\"></div>",
57
+ "x-first-paint": "A message's width: the block is as wide as its flow."
58
+ },
59
+ {
60
+ "name": "ask",
61
+ "title": "Заполнить и отправить",
62
+ "settings": {
63
+ "mode": "edit",
64
+ "heading": "Новая командировка",
65
+ "block-id": "b1",
66
+ "fields": [
67
+ {
68
+ "key": "city",
69
+ "label": "Город",
70
+ "propertyType": "string",
71
+ "width": "half",
72
+ "required": true,
73
+ "placeholder": "Куда",
74
+ "value": null
75
+ },
76
+ {
77
+ "key": "from",
78
+ "label": "Выезд",
79
+ "propertyType": "date",
80
+ "width": "half",
81
+ "required": true,
82
+ "value": null
83
+ },
84
+ {
85
+ "key": "nights",
86
+ "label": "Ночей",
87
+ "propertyType": "integer",
88
+ "width": "half",
89
+ "value": 2
90
+ },
91
+ {
92
+ "key": "goal",
93
+ "label": "Цель поездки",
94
+ "propertyType": "text",
95
+ "width": "full",
96
+ "placeholder": "Например, встреча с заказчиком",
97
+ "value": null
98
+ }
99
+ ]
100
+ },
101
+ "expect": {
102
+ "name": "Новая командировка",
103
+ "host": {
104
+ "state": "open"
105
+ }
106
+ },
107
+ "parent": "<div style=\"inline-size: 32rem; max-inline-size: 100%\"></div>"
108
+ },
109
+ {
110
+ "name": "answered",
111
+ "title": "Ответ отправлен",
112
+ "settings": {
113
+ "mode": "edit",
114
+ "state": "accepted",
115
+ "heading": "Новая командировка",
116
+ "block-id": "b1",
117
+ "fields": [
118
+ {
119
+ "key": "city",
120
+ "label": "Город",
121
+ "propertyType": "string",
122
+ "width": "half",
123
+ "required": true,
124
+ "placeholder": "Куда",
125
+ "value": "Казань"
126
+ },
127
+ {
128
+ "key": "from",
129
+ "label": "Выезд",
130
+ "propertyType": "date",
131
+ "width": "half",
132
+ "required": true,
133
+ "value": "2026-11-20"
134
+ },
135
+ {
136
+ "key": "nights",
137
+ "label": "Ночей",
138
+ "propertyType": "integer",
139
+ "width": "half",
140
+ "value": 3
141
+ },
142
+ {
143
+ "key": "goal",
144
+ "label": "Цель поездки",
145
+ "propertyType": "text",
146
+ "width": "full",
147
+ "placeholder": "Например, встреча с заказчиком",
148
+ "value": "Встреча с заказчиком"
149
+ }
150
+ ]
151
+ },
152
+ "expect": {
153
+ "name": "Новая командировка",
154
+ "texts": {
155
+ ".sf-view-flow__status span": "Отправлено"
156
+ }
157
+ },
158
+ "parent": "<div style=\"inline-size: 32rem; max-inline-size: 100%\"></div>"
159
+ },
160
+ {
161
+ "name": "groups",
162
+ "title": "Группы",
163
+ "settings": {
164
+ "heading": "Заказ № 10458",
165
+ "groups": [
166
+ {
167
+ "key": "order",
168
+ "label": "Заказ",
169
+ "fields": [
170
+ {
171
+ "key": "buyer",
172
+ "label": "Покупатель",
173
+ "propertyType": "string",
174
+ "width": "half",
175
+ "value": "Анна Белова"
176
+ },
177
+ {
178
+ "key": "amount",
179
+ "label": "Сумма",
180
+ "propertyType": "number",
181
+ "unit": "₽",
182
+ "width": "half",
183
+ "value": 12490
184
+ }
185
+ ]
186
+ },
187
+ {
188
+ "key": "delivery",
189
+ "label": "Доставка",
190
+ "collapsible": true,
191
+ "collapsed": true,
192
+ "fields": [
193
+ {
194
+ "key": "city",
195
+ "label": "Город",
196
+ "propertyType": "string",
197
+ "width": "half",
198
+ "value": "Казань"
199
+ },
200
+ {
201
+ "key": "way",
202
+ "label": "Способ",
203
+ "propertyType": "string",
204
+ "width": "half",
205
+ "value": "Курьер"
206
+ }
207
+ ]
208
+ }
209
+ ]
210
+ },
211
+ "expect": {
212
+ "name": "Заказ № 10458"
213
+ },
214
+ "parent": "<div style=\"inline-size: 32rem; max-inline-size: 100%\"></div>"
215
+ },
216
+ {
217
+ "name": "expired",
218
+ "title": "Время ответа истекло",
219
+ "settings": {
220
+ "mode": "edit",
221
+ "heading": "Новая командировка",
222
+ "block-id": "b2",
223
+ "expires-at": "2020-01-01T00:00:00Z",
224
+ "fields": [
225
+ {
226
+ "key": "city",
227
+ "label": "Город",
228
+ "propertyType": "string",
229
+ "width": "half",
230
+ "required": true,
231
+ "placeholder": "Куда",
232
+ "value": null
233
+ },
234
+ {
235
+ "key": "from",
236
+ "label": "Выезд",
237
+ "propertyType": "date",
238
+ "width": "half",
239
+ "required": true,
240
+ "value": null
241
+ },
242
+ {
243
+ "key": "nights",
244
+ "label": "Ночей",
245
+ "propertyType": "integer",
246
+ "width": "half",
247
+ "value": 2
248
+ },
249
+ {
250
+ "key": "goal",
251
+ "label": "Цель поездки",
252
+ "propertyType": "text",
253
+ "width": "full",
254
+ "placeholder": "Например, встреча с заказчиком",
255
+ "value": null
256
+ }
257
+ ]
258
+ },
259
+ "expect": {
260
+ "name": "Новая командировка",
261
+ "events": [
262
+ "expired"
263
+ ]
264
+ },
265
+ "parent": "<div style=\"inline-size: 32rem; max-inline-size: 100%\"></div>"
266
+ },
267
+ {
268
+ "name": "untitled",
269
+ "title": "Без заголовка",
270
+ "settings": {
271
+ "fields": [
272
+ {
273
+ "key": "employee",
274
+ "label": "Сотрудник",
275
+ "propertyType": "string",
276
+ "width": "full",
277
+ "value": "Ирина Волкова"
278
+ },
279
+ {
280
+ "key": "from",
281
+ "label": "С",
282
+ "propertyType": "date",
283
+ "width": "half",
284
+ "value": "2026-12-21"
285
+ },
286
+ {
287
+ "key": "to",
288
+ "label": "По",
289
+ "propertyType": "date",
290
+ "width": "half",
291
+ "value": "2027-01-08"
292
+ }
293
+ ]
294
+ },
295
+ "expect": {
296
+ "name": "Данные"
297
+ },
298
+ "parent": "<div style=\"inline-size: 32rem; max-inline-size: 100%\"></div>"
299
+ }
300
+ ],
301
+ "pages": [
302
+ {
303
+ "name": "conformance",
304
+ "title": "sf-view-flow against component.json (standard simai.smart-component 1.4 §12, gate 20)",
305
+ "format": "stands/15-scenarios (A4 modal: a page, probes in steps); the runner is ui-source tests/browser/manifest-probes.browser.mjs, run for the package by scripts/run-manifest-probes.mjs",
306
+ "page": {
307
+ "lang": "ru",
308
+ "body": [
309
+ "<main>",
310
+ " <sf-view-flow id=\"b\" mode=\"edit\" heading=\"Новая командировка\" block-id=\"b1\" fields='[{\"key\": \"city\", \"label\": \"Город\", \"propertyType\": \"string\", \"width\": \"half\", \"required\": true, \"placeholder\": \"Куда\", \"value\": null}, {\"key\": \"from\", \"label\": \"Выезд\", \"propertyType\": \"date\", \"width\": \"half\", \"required\": true, \"value\": null}, {\"key\": \"nights\", \"label\": \"Ночей\", \"propertyType\": \"integer\", \"width\": \"half\", \"value\": 2}, {\"key\": \"goal\", \"label\": \"Цель поездки\", \"propertyType\": \"text\", \"width\": \"full\", \"placeholder\": \"Например, встреча с заказчиком\", \"value\": null}]'></sf-view-flow>",
311
+ " <sf-view-flow id=\"c\" mode=\"edit\" block-id=\"b2\" cancelable=\"false\" fields='[{\"key\": \"nights\", \"label\": \"Ночей\", \"propertyType\": \"integer\", \"width\": \"half\", \"value\": 2}]'></sf-view-flow>",
312
+ " <button id=\"page-send\" type=\"button\" data-sf-action=\"c.confirm\">Отправить</button>",
313
+ "</main>"
314
+ ]
315
+ },
316
+ "checks": [
317
+ {
318
+ "id": "p1-required-stops-send",
319
+ "title": "Send with the required fields empty sends nothing: each says «Заполните поле», the status line «Заполните отмеченные поля», and the block stays open",
320
+ "covers": "actions.confirm refused invalid, required, accessibility.name heading",
321
+ "steps": [
322
+ {
323
+ "script": "(document.activeElement?.blur?.(), true)",
324
+ "wait": 60,
325
+ "check": "document.querySelector('#b section').getAttribute('aria-labelledby') === document.querySelector('#b [role=heading]').id"
326
+ },
327
+ {
328
+ "action": {
329
+ "name": "confirm"
330
+ },
331
+ "target": "#b",
332
+ "wait": 120,
333
+ "expect": {
334
+ "returns": {
335
+ "outcome": "refused",
336
+ "reason": "invalid"
337
+ },
338
+ "events": [
339
+ {
340
+ "name": "sf-view-flow-answer",
341
+ "count": 0
342
+ }
343
+ ]
344
+ },
345
+ "check": "document.querySelector('#b').state === 'open' && document.querySelectorAll('#b .sf-view-flow__error').length === 2 && document.querySelector('#b .sf-view-flow__status span').textContent.trim() === 'Заполните отмеченные поля'"
346
+ }
347
+ ]
348
+ },
349
+ {
350
+ "id": "p2-cancel",
351
+ "title": "Cancel answers {id, accepted: false}; the block is pending, then declined when the host settles it, and shows only «Отменено»",
352
+ "covers": "actions.cancel, events.answer, actions.settle",
353
+ "steps": [
354
+ {
355
+ "action": {
356
+ "name": "cancel"
357
+ },
358
+ "target": "#b",
359
+ "expect": {
360
+ "returns": {
361
+ "outcome": "applied"
362
+ },
363
+ "events": [
364
+ {
365
+ "name": "sf-view-flow-answer",
366
+ "count": 1,
367
+ "detail": {
368
+ "id": "b1",
369
+ "accepted": false
370
+ }
371
+ }
372
+ ]
373
+ },
374
+ "check": "document.querySelector('#b').state === 'pending'"
375
+ },
376
+ {
377
+ "action": {
378
+ "name": "settle",
379
+ "with": {
380
+ "result": {
381
+ "answer": "applied"
382
+ }
383
+ }
384
+ },
385
+ "target": "#b",
386
+ "wait": 120,
387
+ "expect": {
388
+ "returns": {
389
+ "outcome": "applied"
390
+ }
391
+ },
392
+ "check": "document.querySelector('#b').state === 'declined' && !document.querySelector('#b .sf-view-flow__groups') && document.querySelector('#b .sf-view-flow__status span').textContent.trim() === 'Отменено'"
393
+ }
394
+ ]
395
+ },
396
+ {
397
+ "id": "p3-page-trigger-send",
398
+ "title": "a page button data-sf-action=\"c.confirm\" sends the untitled block with its values; without Cancel there is one answer; the block is named «Данные»",
399
+ "covers": "actions.confirm (§8 page trigger), cancelable false, accessibility.name runtime.block-label",
400
+ "steps": [
401
+ {
402
+ "script": "(document.activeElement?.blur?.(), true)",
403
+ "wait": 60,
404
+ "check": "document.querySelectorAll('#c sf-button').length === 1 && document.querySelector('#c section').getAttribute('aria-label') === 'Данные'"
405
+ },
406
+ {
407
+ "user": "click:#page-send",
408
+ "expect": {
409
+ "events": [
410
+ {
411
+ "name": "sf-view-flow-answer",
412
+ "count": 1,
413
+ "detail": {
414
+ "id": "b2",
415
+ "accepted": true,
416
+ "values": {
417
+ "nights": 2
418
+ }
419
+ }
420
+ }
421
+ ]
422
+ },
423
+ "check": "document.querySelector('#c').state === 'pending'"
424
+ }
425
+ ]
426
+ },
427
+ {
428
+ "id": "p4-expired",
429
+ "title": "an open block past expires-at reads as expired, says sf-view-flow-expired once and offers no answer",
430
+ "covers": "events.expired (own verb)",
431
+ "steps": [
432
+ {
433
+ "script": "(document.querySelector('#c').state = 'open', document.querySelector('#c').setAttribute('expires-at', '2020-01-01T00:00:00Z'), true)",
434
+ "expect": {
435
+ "events": [
436
+ {
437
+ "name": "sf-view-flow-expired",
438
+ "count": 1,
439
+ "detail": {
440
+ "id": "b2"
441
+ }
442
+ }
443
+ ]
444
+ },
445
+ "check": "document.querySelector('#c').shownState === 'expired' && document.querySelector('#c .sf-view-flow__actions').hidden"
446
+ }
447
+ ]
448
+ }
449
+ ]
450
+ }
451
+ ]
452
+ }