1
+ import { useState } from "react" ;
2
+ import { computed } from "nanostores" ;
1
3
import { useStore } from "@nanostores/react" ;
2
- import { Box , Select , theme } from "@webstudio-is/design-system" ;
4
+ import { Box , Combobox , Select , theme } from "@webstudio-is/design-system" ;
3
5
import { elementsByTag } from "@webstudio-is/html-data" ;
4
- import { $selectedInstance } from "~/shared/awareness" ;
6
+ import { tags } from "@webstudio-is/sdk" ;
7
+ import { $selectedInstance , $selectedInstancePath } from "~/shared/awareness" ;
5
8
import { updateWebstudioData } from "~/shared/instance-utils" ;
9
+ import { isTreeSatisfyingContentModel } from "~/shared/content-model" ;
10
+ import {
11
+ $instances ,
12
+ $props ,
13
+ $registeredComponentMetas ,
14
+ } from "~/shared/nano-states" ;
6
15
import { type ControlProps , VerticalLayout } from "../shared" ;
7
16
import { FieldLabel } from "../property-label" ;
8
17
18
+ const $satisfyingTags = computed (
19
+ [ $selectedInstancePath , $instances , $props , $registeredComponentMetas ] ,
20
+ ( instancePath , instances , props , metas ) => {
21
+ const satisfyingTags : string [ ] = [ ] ;
22
+ if ( instancePath === undefined ) {
23
+ return satisfyingTags ;
24
+ }
25
+ const [ { instance, instanceSelector } ] = instancePath ;
26
+ const newInstances = new Map ( instances ) ;
27
+ for ( const tag of tags ) {
28
+ newInstances . set ( instance . id , { ...instance , tag } ) ;
29
+ const isSatisfying = isTreeSatisfyingContentModel ( {
30
+ instances : newInstances ,
31
+ props,
32
+ metas,
33
+ instanceSelector,
34
+ } ) ;
35
+ if ( isSatisfying ) {
36
+ satisfyingTags . push ( tag ) ;
37
+ }
38
+ }
39
+ return satisfyingTags ;
40
+ }
41
+ ) ;
42
+
9
43
export const TagControl = ( { meta, prop } : ControlProps < "tag" > ) => {
10
44
const instance = useStore ( $selectedInstance ) ;
11
45
const propTag = prop ?. type === "string" ? prop . value : undefined ;
12
46
const instanceTag = instance ?. tag ;
13
47
const defaultTag = meta . options [ 0 ] ;
14
- const value = propTag ?? instanceTag ?? defaultTag ;
48
+ const computedTag = instanceTag ?? propTag ?? defaultTag ;
49
+ const satisfyingTags = useStore ( $satisfyingTags ) ;
50
+ const options = meta . options . filter ( ( tag ) => satisfyingTags . includes ( tag ) ) ;
51
+ const [ value , setValue ] = useState < undefined | string > ( ) ;
52
+ const updateTag = ( value : string ) => {
53
+ if ( instance === undefined ) {
54
+ return ;
55
+ }
56
+ const instanceId = instance . id ;
57
+ updateWebstudioData ( ( data ) => {
58
+ // clean legacy <Box tag> and <Text tag>
59
+ if ( prop ) {
60
+ data . props . delete ( prop . id ) ;
61
+ }
62
+ const instance = data . instances . get ( instanceId ) ;
63
+ if ( instance ) {
64
+ instance . tag = value ;
65
+ }
66
+ } ) ;
67
+ } ;
15
68
return (
16
69
< VerticalLayout
17
70
label = {
@@ -20,32 +73,36 @@ export const TagControl = ({ meta, prop }: ControlProps<"tag">) => {
20
73
</ FieldLabel >
21
74
}
22
75
>
23
- < Select
24
- fullWidth
25
- value = { value }
26
- options = { meta . options }
27
- onChange = { ( value ) => {
28
- if ( instance === undefined ) {
29
- return ;
30
- }
31
- const instanceId = instance . id ;
32
- updateWebstudioData ( ( data ) => {
33
- // clean legacy <Box tag> and <Text tag>
34
- if ( prop ) {
35
- data . props . delete ( prop . id ) ;
36
- }
37
- const instance = data . instances . get ( instanceId ) ;
38
- if ( instance ) {
39
- instance . tag = value ;
40
- }
41
- } ) ;
42
- } }
43
- getDescription = { ( item ) => (
44
- < Box css = { { width : theme . spacing [ 28 ] } } >
45
- { elementsByTag [ item ] ?. description }
46
- </ Box >
47
- ) }
48
- />
76
+ { options . length > 10 ? (
77
+ < Combobox < string >
78
+ defaultHighlightedIndex = { 0 }
79
+ getItems = { ( ) => options }
80
+ onItemSelect = { ( item ) => {
81
+ updateTag ( item ) ;
82
+ setValue ( undefined ) ;
83
+ } }
84
+ itemToString = { ( item ) => item ?? options [ 0 ] }
85
+ value = { value ?? computedTag }
86
+ onChange = { ( value ) => setValue ( value ?? undefined ) }
87
+ getDescription = { ( item ) => (
88
+ < Box css = { { width : theme . spacing [ 28 ] } } >
89
+ { elementsByTag [ item ?? "" ] ?. description }
90
+ </ Box >
91
+ ) }
92
+ />
93
+ ) : (
94
+ < Select
95
+ fullWidth
96
+ value = { computedTag }
97
+ options = { options }
98
+ onChange = { updateTag }
99
+ getDescription = { ( item ) => (
100
+ < Box css = { { width : theme . spacing [ 28 ] } } >
101
+ { elementsByTag [ item ] ?. description }
102
+ </ Box >
103
+ ) }
104
+ />
105
+ ) }
49
106
</ VerticalLayout >
50
107
) ;
51
108
} ;
0 commit comments