@simai-studio/ui 5.20261023.214 → 5.20261025.207
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/distr/component/button-group/css/button-group.min.css +1 -1
- package/distr/core/contracts/public-variables.v1.json +88 -0
- package/distr/core/contracts/third-party-notices.v1.json +1 -1
- package/distr/core/css/core.css +3 -2
- package/distr/core/js/composition/overlay.mjs +472 -24
- package/distr/core/js/composition/sortable-inserter.mjs +534 -0
- package/distr/core/js/core-loader.js +1 -1
- package/distr/core/js/core-loader.min.js +1 -1
- package/distr/core/js/core-rules.js +242 -185
- package/distr/core/js/core-rules.min.js +1 -1
- package/distr/core/js/open-surface-mark.js +88 -0
- package/distr/core/js/template-utilities.generated.json +3 -0
- package/distr/rule/js/rule.min.js +1 -1
- package/distr/rule/rule.json +47 -1
- package/distr/smart/color/css/color.min.css +1 -0
- package/distr/smart/color/js/color.min.js +36 -17
- package/distr/smart/color/smart.manifest.json +45 -4
- package/distr/smart/color/template/default.js +13 -2
- package/distr/smart/composition-overlay/css/composition-overlay.min.css +1 -1
- package/distr/smart/composition-overlay/js/composition-overlay.min.js +1 -1
- package/distr/smart/composition-overlay/smart.manifest.json +178 -11
- package/distr/smart/dropdown/css/dropdown.min.css +1 -0
- package/distr/smart/dropdown/js/dropdown.min.js +41 -21
- package/distr/smart/dropdown/smart.manifest.json +62 -3
- package/distr/smart/dropdown/template/default.js +5 -1
- package/distr/smart/field-renderers/js/field-renderers.min.js +54 -42
- package/distr/smart/image-field/css/image-field.min.css +1 -0
- package/distr/smart/image-field/js/image-field.min.js +62 -0
- package/distr/smart/image-field/smart.manifest.json +519 -0
- package/distr/smart/image-field/studio.fixture.json +15 -0
- package/distr/smart/image-field/studio.scenario.json +22 -0
- package/distr/smart/image-field/template/default.js +125 -0
- package/distr/smart/inputs/css/inputs.min.css +1 -0
- package/distr/smart/inputs/js/inputs.min.js +93 -75
- package/distr/smart/inputs/smart.manifest.json +62 -3
- package/distr/smart/inputs/template/default.js +8 -2
- package/distr/smart/sortable/css/sortable.min.css +1 -1
- package/distr/smart/sortable/js/sortable.min.js +1 -1
- package/distr/smart/sortable/smart.manifest.json +101 -14
- package/distr/smart/switch/css/switch.min.css +1 -0
- package/distr/smart/switch/js/switch.min.js +31 -13
- package/distr/smart/switch/smart.manifest.json +62 -3
- package/distr/smart/switch/template/default.js +2 -0
- package/distr/smart/textarea/css/textarea.min.css +1 -0
- package/distr/smart/textarea/js/textarea.min.js +51 -32
- package/distr/smart/textarea/smart.manifest.json +62 -3
- package/distr/smart/textarea/template/default.js +8 -1
- package/package-manifest.json +50 -39
- package/package.json +5 -4
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 2,
|
|
3
|
+
"id": "smart.image-field",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"title": "Image field",
|
|
6
|
+
"summary": "A form field for one image: a thumbnail with the file name and size, «Заменить…» and «Убрать», a file dropped anywhere on it replaces the image; label, help, error, required, disabled and read-only as every field has them. It raises the chosen File; the host uploads it.",
|
|
7
|
+
"kind": "smart",
|
|
8
|
+
"reuse_mode": "whole",
|
|
9
|
+
"lifecycle": "experimental",
|
|
10
|
+
"tags": [
|
|
11
|
+
"image",
|
|
12
|
+
"file",
|
|
13
|
+
"field",
|
|
14
|
+
"upload",
|
|
15
|
+
"form"
|
|
16
|
+
],
|
|
17
|
+
"capabilities": [
|
|
18
|
+
"render",
|
|
19
|
+
"interaction",
|
|
20
|
+
"keyboard",
|
|
21
|
+
"theme",
|
|
22
|
+
"responsive"
|
|
23
|
+
],
|
|
24
|
+
"custom_element": "sf-image-field",
|
|
25
|
+
"templates": [
|
|
26
|
+
"default"
|
|
27
|
+
],
|
|
28
|
+
"exported_parts": [],
|
|
29
|
+
"composition": {
|
|
30
|
+
"uses": [
|
|
31
|
+
"smart.buttons",
|
|
32
|
+
"smart.icons"
|
|
33
|
+
],
|
|
34
|
+
"public_parts": []
|
|
35
|
+
},
|
|
36
|
+
"inputs": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"template": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"default": "default"
|
|
42
|
+
},
|
|
43
|
+
"value": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"default": "",
|
|
46
|
+
"description": "The image, in the file shape {value, label, mime, size, href}: value the host's id, label the file name (the thumbnail's alt), mime its type, size bytes or text the host formatted, href the address it is shown from. The attribute takes that shape as JSON, or a bare address; the property takes the object, or null for none. Set from code it fires nothing."
|
|
47
|
+
},
|
|
48
|
+
"name": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"default": "",
|
|
51
|
+
"description": "The form name the chosen file is submitted under, by the platform file input inside."
|
|
52
|
+
},
|
|
53
|
+
"label": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"default": "",
|
|
56
|
+
"description": "The field label above the image; it names the group. Without it the group is named «Изображение» / Image."
|
|
57
|
+
},
|
|
58
|
+
"hint": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"default": "",
|
|
61
|
+
"description": "Help text under the field; slot `hint` takes markup instead."
|
|
62
|
+
},
|
|
63
|
+
"accept": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "What the file dialog offers and a drop accepts: `.ext`, `type/*` or `type/sub`, comma separated.",
|
|
66
|
+
"default": "image/*"
|
|
67
|
+
},
|
|
68
|
+
"size": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"default": "1",
|
|
71
|
+
"description": "Field size: 1/2, 1 or 2, as every form field.",
|
|
72
|
+
"enum": [
|
|
73
|
+
"1/2",
|
|
74
|
+
"1",
|
|
75
|
+
"2"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"meta": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"default": "",
|
|
81
|
+
"description": "The line under the name; by default the image's dimensions once it loads and its size («450×300 · 2 КБ»)."
|
|
82
|
+
},
|
|
83
|
+
"empty-text": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"default": "",
|
|
86
|
+
"description": "The words of the empty field; «Изображение не выбрано» / No image chosen by default."
|
|
87
|
+
},
|
|
88
|
+
"required": {
|
|
89
|
+
"type": "boolean",
|
|
90
|
+
"default": false,
|
|
91
|
+
"description": "The field must hold an image: the platform input is required while it is empty, so a form will not submit without one."
|
|
92
|
+
},
|
|
93
|
+
"disabled": {
|
|
94
|
+
"type": "boolean",
|
|
95
|
+
"default": false,
|
|
96
|
+
"description": "Shown and not changed: the buttons are disabled and a drop is refused."
|
|
97
|
+
},
|
|
98
|
+
"readonly": {
|
|
99
|
+
"type": "boolean",
|
|
100
|
+
"default": false,
|
|
101
|
+
"description": "Shown without buttons; a drop is refused."
|
|
102
|
+
},
|
|
103
|
+
"error": {
|
|
104
|
+
"type": "boolean",
|
|
105
|
+
"default": false,
|
|
106
|
+
"description": "The error state; the same as invalid."
|
|
107
|
+
},
|
|
108
|
+
"invalid": {
|
|
109
|
+
"type": "boolean",
|
|
110
|
+
"default": false,
|
|
111
|
+
"description": "The error state: the edge and the message take the error colour and the group carries aria-invalid."
|
|
112
|
+
},
|
|
113
|
+
"error-message": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"default": "",
|
|
116
|
+
"description": "The error shown in place of the hint while the field is invalid; slot `error` takes markup instead."
|
|
117
|
+
},
|
|
118
|
+
"aria-label": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"default": "",
|
|
121
|
+
"description": "The group's accessible name when no label is drawn inside the field: a form that prints the label beside it (the Framework's field renderers) names the field without a second, visible label."
|
|
122
|
+
},
|
|
123
|
+
"root-class": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"default": ""
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"additionalProperties": false
|
|
129
|
+
},
|
|
130
|
+
"item_contract": {},
|
|
131
|
+
"contracts": {
|
|
132
|
+
"value_shape": {
|
|
133
|
+
"const": "file_value_v1"
|
|
134
|
+
},
|
|
135
|
+
"value_owner": {
|
|
136
|
+
"const": "host"
|
|
137
|
+
},
|
|
138
|
+
"upload_owner": {
|
|
139
|
+
"const": "host"
|
|
140
|
+
},
|
|
141
|
+
"form_owner": {
|
|
142
|
+
"const": "native_file_input"
|
|
143
|
+
},
|
|
144
|
+
"field_states": {
|
|
145
|
+
"type": "string",
|
|
146
|
+
"enum": [
|
|
147
|
+
"empty",
|
|
148
|
+
"filled",
|
|
149
|
+
"dragover",
|
|
150
|
+
"readonly",
|
|
151
|
+
"disabled",
|
|
152
|
+
"invalid"
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"methods": {
|
|
157
|
+
"setValue": {
|
|
158
|
+
"summary": "Set the image from code, in the file shape or as an address; null empties the field. Fires nothing, as a value set from code is not a change. Returns the element.",
|
|
159
|
+
"arguments": [
|
|
160
|
+
{
|
|
161
|
+
"name": "value",
|
|
162
|
+
"type": [
|
|
163
|
+
"object",
|
|
164
|
+
"string",
|
|
165
|
+
"null"
|
|
166
|
+
],
|
|
167
|
+
"required": true
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"returns": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"description": "The element, for chaining."
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"clear": {
|
|
176
|
+
"summary": "Empty the field from code; the same as setValue(null). Returns the element.",
|
|
177
|
+
"arguments": [],
|
|
178
|
+
"returns": {
|
|
179
|
+
"type": "object",
|
|
180
|
+
"description": "The element, for chaining."
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"setError": {
|
|
184
|
+
"summary": "Show an error message and the invalid state, or clear both with an empty message. Writes error-message and invalid. Returns the element.",
|
|
185
|
+
"arguments": [
|
|
186
|
+
{
|
|
187
|
+
"name": "message",
|
|
188
|
+
"type": "string",
|
|
189
|
+
"required": false
|
|
190
|
+
}
|
|
191
|
+
],
|
|
192
|
+
"returns": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"description": "The element, for chaining."
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"choose": {
|
|
198
|
+
"summary": "Open the file dialog, as «Заменить…» and «Выбрать…» do. Returns false when the field is disabled or read-only.",
|
|
199
|
+
"arguments": [],
|
|
200
|
+
"returns": {
|
|
201
|
+
"type": "boolean"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"removeImage": {
|
|
205
|
+
"summary": "Remove the image as the person would with «Убрать»: input, change and sf-image-field:remove fire, focus goes to «Выбрать…». Returns whether there was an image to remove.",
|
|
206
|
+
"arguments": [],
|
|
207
|
+
"returns": {
|
|
208
|
+
"type": "boolean"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"takeFile": {
|
|
212
|
+
"summary": "Take a File as the person would by choosing it (source input, drop or another the host names): the thumbnail shows it from an object URL and input, change and sf-image-field:select fire. A file the accept list does not name is rejected instead. Returns whether it was taken.",
|
|
213
|
+
"arguments": [
|
|
214
|
+
{
|
|
215
|
+
"name": "file",
|
|
216
|
+
"type": "object",
|
|
217
|
+
"required": true
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "source",
|
|
221
|
+
"type": "string",
|
|
222
|
+
"required": false
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
"returns": {
|
|
226
|
+
"type": "boolean"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"events": {
|
|
231
|
+
"change": {
|
|
232
|
+
"summary": "The image was chosen, dropped or removed: once per change the person made, a CustomEvent on the host carrying the value, the File and the form name. The platform input's own change does not leave the host.",
|
|
233
|
+
"payload": {
|
|
234
|
+
"type": "object",
|
|
235
|
+
"properties": {
|
|
236
|
+
"value": {
|
|
237
|
+
"type": [
|
|
238
|
+
"object",
|
|
239
|
+
"null"
|
|
240
|
+
],
|
|
241
|
+
"description": "The new value in the file shape; a chosen file carries `file` and an object URL in `href` until the host writes the stored value. null after a removal."
|
|
242
|
+
},
|
|
243
|
+
"file": {
|
|
244
|
+
"type": [
|
|
245
|
+
"object",
|
|
246
|
+
"null"
|
|
247
|
+
],
|
|
248
|
+
"description": "The File chosen, or null after a removal."
|
|
249
|
+
},
|
|
250
|
+
"source": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"enum": [
|
|
253
|
+
"input",
|
|
254
|
+
"drop",
|
|
255
|
+
"remove"
|
|
256
|
+
]
|
|
257
|
+
},
|
|
258
|
+
"name": {
|
|
259
|
+
"type": "string"
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"canonical": true
|
|
264
|
+
},
|
|
265
|
+
"input": {
|
|
266
|
+
"summary": "Sent with every change, before it, with the same detail, as a native file input sends input and change together.",
|
|
267
|
+
"payload": {
|
|
268
|
+
"type": "object",
|
|
269
|
+
"properties": {
|
|
270
|
+
"value": {
|
|
271
|
+
"type": [
|
|
272
|
+
"object",
|
|
273
|
+
"null"
|
|
274
|
+
],
|
|
275
|
+
"description": "The new value in the file shape; a chosen file carries `file` and an object URL in `href` until the host writes the stored value. null after a removal."
|
|
276
|
+
},
|
|
277
|
+
"file": {
|
|
278
|
+
"type": [
|
|
279
|
+
"object",
|
|
280
|
+
"null"
|
|
281
|
+
],
|
|
282
|
+
"description": "The File chosen, or null after a removal."
|
|
283
|
+
},
|
|
284
|
+
"source": {
|
|
285
|
+
"type": "string",
|
|
286
|
+
"enum": [
|
|
287
|
+
"input",
|
|
288
|
+
"drop",
|
|
289
|
+
"remove"
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
"name": {
|
|
293
|
+
"type": "string"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
"sf-image-field:select": {
|
|
299
|
+
"summary": "A file was chosen or dropped: the File to upload. The host uploads it and sets the stored value.",
|
|
300
|
+
"payload": {
|
|
301
|
+
"type": "object",
|
|
302
|
+
"properties": {
|
|
303
|
+
"file": {
|
|
304
|
+
"type": "object"
|
|
305
|
+
},
|
|
306
|
+
"source": {
|
|
307
|
+
"type": "string",
|
|
308
|
+
"enum": [
|
|
309
|
+
"input",
|
|
310
|
+
"drop"
|
|
311
|
+
]
|
|
312
|
+
},
|
|
313
|
+
"value": {
|
|
314
|
+
"type": "object"
|
|
315
|
+
},
|
|
316
|
+
"name": {
|
|
317
|
+
"type": "string"
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
"sf-image-field:remove": {
|
|
323
|
+
"summary": "The person removed the image; detail carries the value it held.",
|
|
324
|
+
"payload": {
|
|
325
|
+
"type": "object",
|
|
326
|
+
"properties": {
|
|
327
|
+
"value": {
|
|
328
|
+
"type": "object"
|
|
329
|
+
},
|
|
330
|
+
"name": {
|
|
331
|
+
"type": "string"
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
"sf-image-field:reject": {
|
|
337
|
+
"summary": "Files the accept list does not name were left out, as the platform dialog would not have offered them; announced politely.",
|
|
338
|
+
"payload": {
|
|
339
|
+
"type": "object",
|
|
340
|
+
"properties": {
|
|
341
|
+
"files": {
|
|
342
|
+
"type": "array"
|
|
343
|
+
},
|
|
344
|
+
"accept": {
|
|
345
|
+
"type": "string"
|
|
346
|
+
},
|
|
347
|
+
"source": {
|
|
348
|
+
"type": "string"
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
"state": {
|
|
355
|
+
"machines": {
|
|
356
|
+
"value": [
|
|
357
|
+
"empty",
|
|
358
|
+
"filled"
|
|
359
|
+
],
|
|
360
|
+
"drag": [
|
|
361
|
+
"idle",
|
|
362
|
+
"dragover"
|
|
363
|
+
]
|
|
364
|
+
},
|
|
365
|
+
"visual_states": [
|
|
366
|
+
"default",
|
|
367
|
+
"empty",
|
|
368
|
+
"active",
|
|
369
|
+
"disabled",
|
|
370
|
+
"error"
|
|
371
|
+
]
|
|
372
|
+
},
|
|
373
|
+
"persistence": {
|
|
374
|
+
"mode": "none",
|
|
375
|
+
"source_of_truth": "host",
|
|
376
|
+
"network_owner": "host",
|
|
377
|
+
"revision_field": null
|
|
378
|
+
},
|
|
379
|
+
"ownership": {
|
|
380
|
+
"source": "simai/ui-source",
|
|
381
|
+
"data": "host",
|
|
382
|
+
"effects": "host",
|
|
383
|
+
"forbidden_client_responsibilities": [
|
|
384
|
+
"backend access decisions",
|
|
385
|
+
"uploading the file",
|
|
386
|
+
"choosing where the file is stored"
|
|
387
|
+
]
|
|
388
|
+
},
|
|
389
|
+
"security": {
|
|
390
|
+
"permissions_owner": "host",
|
|
391
|
+
"untrusted_inputs": [
|
|
392
|
+
"props",
|
|
393
|
+
"children",
|
|
394
|
+
"dropped files"
|
|
395
|
+
],
|
|
396
|
+
"network_policy": "deny"
|
|
397
|
+
},
|
|
398
|
+
"accessibility": {
|
|
399
|
+
"role": "group",
|
|
400
|
+
"accessible_name": "recommended",
|
|
401
|
+
"keyboard": [
|
|
402
|
+
"Tab",
|
|
403
|
+
"Enter",
|
|
404
|
+
"Space"
|
|
405
|
+
],
|
|
406
|
+
"focus": "A group named by the field label and described by the help or error text. «Заменить…», «Убрать» and «Выбрать…» are real buttons named for what they act on («Заменить изображение «logo.png»»); the thumbnail's alt is the file name. The drop target takes no focus, so it is never a stop or a trap. After a removal focus goes to «Выбрать…». A choice, a removal and a refused file are announced in a polite status."
|
|
407
|
+
},
|
|
408
|
+
"presentation": {
|
|
409
|
+
"themes": [
|
|
410
|
+
"light",
|
|
411
|
+
"dark"
|
|
412
|
+
],
|
|
413
|
+
"locales": [
|
|
414
|
+
"ru",
|
|
415
|
+
"en"
|
|
416
|
+
],
|
|
417
|
+
"responsive": [
|
|
418
|
+
"desktop",
|
|
419
|
+
"mobile",
|
|
420
|
+
"ltr",
|
|
421
|
+
"rtl"
|
|
422
|
+
],
|
|
423
|
+
"skeleton": {
|
|
424
|
+
"shape": "block",
|
|
425
|
+
"while_loading": "none"
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
"adapter": {
|
|
429
|
+
"kind": "generic",
|
|
430
|
+
"protocol": "simai.smart-studio-adapter.v1",
|
|
431
|
+
"module": null
|
|
432
|
+
},
|
|
433
|
+
"artifacts": {
|
|
434
|
+
"fixtures": [
|
|
435
|
+
"studio.fixture.json"
|
|
436
|
+
],
|
|
437
|
+
"scenarios": [
|
|
438
|
+
"studio.scenario.json"
|
|
439
|
+
]
|
|
440
|
+
},
|
|
441
|
+
"readiness": {
|
|
442
|
+
"level": "runnable",
|
|
443
|
+
"required_checks": [
|
|
444
|
+
"manifest",
|
|
445
|
+
"fixture",
|
|
446
|
+
"runtime-upgrade",
|
|
447
|
+
"scenario-smoke",
|
|
448
|
+
"keyboard",
|
|
449
|
+
"responsive",
|
|
450
|
+
"a11y"
|
|
451
|
+
]
|
|
452
|
+
},
|
|
453
|
+
"compatibility": {
|
|
454
|
+
"contract_schema": {
|
|
455
|
+
"id": "https://simai.io/contracts/smart-component-manifest.v2.schema.json",
|
|
456
|
+
"version": 2
|
|
457
|
+
},
|
|
458
|
+
"runtime_pair": {
|
|
459
|
+
"ui_revision": "094f3d549aa7921d2d7f8773972c73711dd6a72e",
|
|
460
|
+
"ui_smart_revision": "856fb5d751fad1c76104c32515f4c1869dd75b6c"
|
|
461
|
+
},
|
|
462
|
+
"aliases": [],
|
|
463
|
+
"deprecations": [],
|
|
464
|
+
"replacement": null,
|
|
465
|
+
"migrations": []
|
|
466
|
+
},
|
|
467
|
+
"provenance": {
|
|
468
|
+
"source_repository": "simai/ui-source",
|
|
469
|
+
"source_revision": "d73527c92056a684b5f34a46ec7315e1ded9be19",
|
|
470
|
+
"source_artifacts": [
|
|
471
|
+
{
|
|
472
|
+
"path": "src/smart/image-field/index.js",
|
|
473
|
+
"sha256": "edde1fb9b0271a7adb1032da4cd82306ffa0908f713780613c4a027d5e4d845a"
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
"path": "src/smart/image-field/value.mjs",
|
|
477
|
+
"sha256": "324074e539f4eccb3b5f1d9baf5e10a933ceb329d07335f9d0256eb54b39b0f6"
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
"path": "src/smart/image-field/js/templates/default.js",
|
|
481
|
+
"sha256": "d3cf5807ddadf3ab09a673f192400c1603911e6a97e7e93df5a9c167b3cfa9b0"
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
"path": "src/smart/image-field/scss/_smart__image_field.scss",
|
|
485
|
+
"sha256": "1410f21416c9bc8d4b307d3299d55311730bb8586260e20876fe706b05a04366"
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
"path": "src/smart/image-field/rule.js",
|
|
489
|
+
"sha256": "2e6c8fae618bfe538306bd8de8aa1515f1ff24f33fa7d42ac575693c3d807291"
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"path": "src/smart/image-field/docs.md",
|
|
493
|
+
"sha256": "3a1cb4064e56747199a000d1155e5dc98979ac53ead25128bf95d69b20e9e646"
|
|
494
|
+
}
|
|
495
|
+
],
|
|
496
|
+
"generated_runtime": []
|
|
497
|
+
},
|
|
498
|
+
"ai_guidance": {
|
|
499
|
+
"use_when": "Use sf-image-field for a form value that is one image -- a logo, a cover, a picture in a block -- shown, replaced and removed in place.",
|
|
500
|
+
"do_not": [
|
|
501
|
+
"Do not copy generated ui-smart code",
|
|
502
|
+
"Do not use it for several files or a transfer list: that is sf-file-upload",
|
|
503
|
+
"Do not expect it to upload: it raises the File and the host uploads it"
|
|
504
|
+
],
|
|
505
|
+
"required_reads": [
|
|
506
|
+
"inputs",
|
|
507
|
+
"events",
|
|
508
|
+
"methods",
|
|
509
|
+
"accessibility",
|
|
510
|
+
"ownership",
|
|
511
|
+
"provenance"
|
|
512
|
+
],
|
|
513
|
+
"minimal_example": "<sf-image-field label=\"Логотип\" name=\"logo\" value='{\"value\":\"42\",\"label\":\"logo.png\",\"mime\":\"image/png\",\"size\":2048,\"href\":\"/upload/logo.png\"}'></sf-image-field>",
|
|
514
|
+
"owner_commands": [
|
|
515
|
+
"npm run contract:verify",
|
|
516
|
+
"npm test"
|
|
517
|
+
]
|
|
518
|
+
}
|
|
519
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "simai.smart-component-fixture.v2",
|
|
3
|
+
"id": "reference",
|
|
4
|
+
"component": "smart.image-field",
|
|
5
|
+
"title": "image field reference",
|
|
6
|
+
"summary": "Deterministic reference fixture for sf-image-field.",
|
|
7
|
+
"locale": "ru",
|
|
8
|
+
"props": {
|
|
9
|
+
"label": "Логотип",
|
|
10
|
+
"name": "logo",
|
|
11
|
+
"hint": "PNG или SVG, до 1 МБ"
|
|
12
|
+
},
|
|
13
|
+
"children": [],
|
|
14
|
+
"state": "default"
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "simai.smart-component-scenario.v1",
|
|
3
|
+
"id": "smoke",
|
|
4
|
+
"component": "smart.image-field",
|
|
5
|
+
"fixture": "reference",
|
|
6
|
+
"viewport": "desktop",
|
|
7
|
+
"actions": [],
|
|
8
|
+
"assertions": [
|
|
9
|
+
{
|
|
10
|
+
"type": "upgraded"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "visible"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "no-console-error"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "no-network"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {html, nothing} from 'lit';
|
|
2
|
+
|
|
3
|
+
// The buttons are one rung below the field, as a field's own trailing controls
|
|
4
|
+
// are: a size 1 field (a 40px control row) carries size 1/2 buttons.
|
|
5
|
+
const BUTTON_SIZE = {'1/2': '1/3', '1': '1/2', '2': '1'};
|
|
6
|
+
|
|
7
|
+
// What stands in the thumbnail when there is no picture to show: an invitation
|
|
8
|
+
// while the field is empty, a broken image when the address did not load, and
|
|
9
|
+
// a plain image glyph for a value the host gave without an address.
|
|
10
|
+
function thumbIcon(context) {
|
|
11
|
+
if (!context.value) return 'add_photo_alternate';
|
|
12
|
+
if (context.broken) return 'broken_image';
|
|
13
|
+
return 'image';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function renderThumb(context) {
|
|
17
|
+
const {value, component} = context;
|
|
18
|
+
const href = value?.href || '';
|
|
19
|
+
if (href && !context.broken) {
|
|
20
|
+
// The image is fitted, never stretched or cropped: a logo 900×300 stays a
|
|
21
|
+
// logo. alt is the file's name, the one thing the field knows about it.
|
|
22
|
+
return html`<img class="sf-image-field-image" src=${href} alt=${context.fileName}
|
|
23
|
+
@load=${(event) => component.onImageLoad(event, href)}
|
|
24
|
+
@error=${() => component.onImageError(href)}>`;
|
|
25
|
+
}
|
|
26
|
+
return html`<i class="sf-icon" aria-hidden="true">${thumbIcon(context)}</i>`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function renderActions(context) {
|
|
30
|
+
const {component, value, words, names, field} = context;
|
|
31
|
+
if (context.readonly) return nothing;
|
|
32
|
+
const size = BUTTON_SIZE[context.size] ?? '1/2';
|
|
33
|
+
const describedBy = context.message ? field.messageId : nothing;
|
|
34
|
+
// Real buttons with names that say what they act on: «Заменить изображение
|
|
35
|
+
// «logo.png»», each beginning with its visible words so a voice command
|
|
36
|
+
// naming what is seen still reaches it.
|
|
37
|
+
return html`
|
|
38
|
+
<span class="sf-image-field-actions">
|
|
39
|
+
${value
|
|
40
|
+
? html`
|
|
41
|
+
<sf-button class="sf-image-field-replace" size=${size} appearance="outline" scheme="on-surface"
|
|
42
|
+
text=${words.replace} aria-label=${names.replace} aria-describedby=${describedBy}
|
|
43
|
+
?disabled=${context.disabled}
|
|
44
|
+
@click=${() => component.choose()}></sf-button>
|
|
45
|
+
<sf-button class="sf-image-field-remove" size=${size} appearance="link" scheme="on-surface"
|
|
46
|
+
text=${words.remove} aria-label=${names.remove}
|
|
47
|
+
?disabled=${context.disabled}
|
|
48
|
+
@click=${() => component.removeImage()}></sf-button>`
|
|
49
|
+
: html`
|
|
50
|
+
<sf-button class="sf-image-field-choose" size=${size} appearance="outline" scheme="on-surface"
|
|
51
|
+
text=${words.choose} aria-label=${names.choose} aria-describedby=${describedBy}
|
|
52
|
+
?disabled=${context.disabled}
|
|
53
|
+
@click=${() => component.choose()}></sf-button>`}
|
|
54
|
+
</span>
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function renderImageFieldTemplate(context) {
|
|
59
|
+
const {component, field, value, words} = context;
|
|
60
|
+
const labelId = `${context.id}-label`;
|
|
61
|
+
const rootClasses = [
|
|
62
|
+
'sf-image-field',
|
|
63
|
+
`sf-image-field--size-${context.size}`,
|
|
64
|
+
value ? 'sf-image-field--filled' : 'sf-image-field--empty',
|
|
65
|
+
field.invalid ? 'error' : '',
|
|
66
|
+
context.dragging ? 'dragover' : '',
|
|
67
|
+
context.disabled ? 'disabled' : '',
|
|
68
|
+
context.readonly ? 'readonly' : '',
|
|
69
|
+
context.rootClass,
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
|
|
72
|
+
const title = context.dragging
|
|
73
|
+
? words.drop
|
|
74
|
+
: value ? context.fileName || words.group : context.emptyText || words.empty;
|
|
75
|
+
const meta = context.dragging ? '' : value ? context.metaText : (context.interactive ? words.emptyHint : '');
|
|
76
|
+
|
|
77
|
+
return html`
|
|
78
|
+
<div class=${rootClasses.join(' ')} style=${context.rootStyle || nothing}>
|
|
79
|
+
${context.label
|
|
80
|
+
? html`
|
|
81
|
+
<span class="sf-image-field-label" id=${labelId}>
|
|
82
|
+
<span class="sf-image-field-label-text">${context.label}</span>
|
|
83
|
+
${field.required ? html`<span class="sf-image-field-required">*</span>` : nothing}
|
|
84
|
+
</span>`
|
|
85
|
+
: nothing}
|
|
86
|
+
|
|
87
|
+
<div class="sf-image-field-well"
|
|
88
|
+
role="group"
|
|
89
|
+
aria-labelledby=${context.label ? labelId : nothing}
|
|
90
|
+
aria-label=${context.label ? nothing : context.names.group}
|
|
91
|
+
aria-describedby=${context.message ? field.messageId : nothing}
|
|
92
|
+
aria-invalid=${field.invalid ? 'true' : nothing}
|
|
93
|
+
@dragenter=${(event) => component.onDragOver(event)}
|
|
94
|
+
@dragover=${(event) => component.onDragOver(event)}
|
|
95
|
+
@dragleave=${(event) => component.onDragLeave(event)}
|
|
96
|
+
@drop=${(event) => component.onDrop(event)}>
|
|
97
|
+
<span class="sf-image-field-thumb">${renderThumb(context)}</span>
|
|
98
|
+
<span class="sf-image-field-body">
|
|
99
|
+
<span class="sf-image-field-name">${title}</span>
|
|
100
|
+
${meta ? html`<span class="sf-image-field-meta">${meta}</span>` : nothing}
|
|
101
|
+
</span>
|
|
102
|
+
${renderActions(context)}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<input type="file"
|
|
106
|
+
class="sf-image-field-input"
|
|
107
|
+
id=${field.controlId}
|
|
108
|
+
name=${context.name || nothing}
|
|
109
|
+
accept=${context.accept || nothing}
|
|
110
|
+
tabindex="-1"
|
|
111
|
+
aria-hidden="true"
|
|
112
|
+
?required=${field.required && !value}
|
|
113
|
+
?disabled=${context.disabled || context.readonly}
|
|
114
|
+
@change=${(event) => component.onInputChange(event)}
|
|
115
|
+
@input=${(event) => component.onInputEvent(event)}>
|
|
116
|
+
|
|
117
|
+
${context.message
|
|
118
|
+
? html`<span id=${field.messageId} class="sf-image-field-message"
|
|
119
|
+
data-field-message=${context.messageKind}>${context.message}</span>`
|
|
120
|
+
: nothing}
|
|
121
|
+
|
|
122
|
+
<span class="sf-image-field-live" role="status" aria-live="polite" aria-atomic="true">${context.announcement}</span>
|
|
123
|
+
</div>
|
|
124
|
+
`;
|
|
125
|
+
}
|