@contember/bindx-react 0.1.24 → 0.1.26

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 (56) hide show
  1. package/dist/hooks/ContemberBindxProvider.d.ts +1 -26
  2. package/dist/hooks/ContemberBindxProvider.d.ts.map +1 -1
  3. package/dist/hooks/index.d.ts +1 -0
  4. package/dist/hooks/index.d.ts.map +1 -1
  5. package/dist/hooks/useAccessor.d.ts +16 -0
  6. package/dist/hooks/useAccessor.d.ts.map +1 -0
  7. package/dist/hooks/useField.d.ts +1 -4
  8. package/dist/hooks/useField.d.ts.map +1 -1
  9. package/dist/hooks/useHasMany.d.ts +2 -5
  10. package/dist/hooks/useHasMany.d.ts.map +1 -1
  11. package/dist/hooks/useHasOne.d.ts +2 -5
  12. package/dist/hooks/useHasOne.d.ts.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/jsx/analyzer.d.ts.map +1 -1
  16. package/dist/jsx/componentBuilder.types.d.ts +16 -12
  17. package/dist/jsx/componentBuilder.types.d.ts.map +1 -1
  18. package/dist/jsx/componentFactory.d.ts.map +1 -1
  19. package/dist/jsx/components/Attribute.d.ts +38 -0
  20. package/dist/jsx/components/Attribute.d.ts.map +1 -0
  21. package/dist/jsx/components/Entity.d.ts +2 -2
  22. package/dist/jsx/components/Entity.d.ts.map +1 -1
  23. package/dist/jsx/components/EntityList.d.ts.map +1 -1
  24. package/dist/jsx/components/Field.d.ts.map +1 -1
  25. package/dist/jsx/components/HasMany.d.ts.map +1 -1
  26. package/dist/jsx/components/HasOne.d.ts.map +1 -1
  27. package/dist/jsx/components/index.d.ts +1 -0
  28. package/dist/jsx/components/index.d.ts.map +1 -1
  29. package/dist/jsx/devAnnotations.d.ts +12 -0
  30. package/dist/jsx/devAnnotations.d.ts.map +1 -0
  31. package/dist/jsx/index.d.ts +1 -0
  32. package/dist/jsx/index.d.ts.map +1 -1
  33. package/dist/jsx/types.d.ts +3 -3
  34. package/dist/jsx/types.d.ts.map +1 -1
  35. package/package.json +3 -3
  36. package/src/hooks/ContemberBindxProvider.tsx +3 -24
  37. package/src/hooks/index.ts +1 -0
  38. package/src/hooks/useAccessor.ts +64 -0
  39. package/src/hooks/useField.ts +4 -20
  40. package/src/hooks/useHasMany.ts +4 -17
  41. package/src/hooks/useHasOne.ts +4 -17
  42. package/src/index.ts +3 -0
  43. package/src/jsx/analyzer.ts +1 -84
  44. package/src/jsx/collectorProxy.ts +2 -0
  45. package/src/jsx/componentBuilder.types.ts +31 -16
  46. package/src/jsx/componentFactory.ts +47 -5
  47. package/src/jsx/components/Attribute.tsx +90 -0
  48. package/src/jsx/components/Entity.tsx +5 -3
  49. package/src/jsx/components/EntityList.tsx +3 -1
  50. package/src/jsx/components/Field.tsx +11 -3
  51. package/src/jsx/components/HasMany.tsx +10 -1
  52. package/src/jsx/components/HasOne.tsx +5 -1
  53. package/src/jsx/components/index.ts +3 -0
  54. package/src/jsx/devAnnotations.ts +33 -0
  55. package/src/jsx/index.ts +1 -0
  56. package/src/jsx/types.ts +3 -3
@@ -2,6 +2,7 @@ import { memo, type ReactElement } from 'react'
2
2
  import type { FieldProps, SelectionFieldMeta, SelectionProvider } from '../types.js'
3
3
  import { FIELD_REF_META, BINDX_COMPONENT } from '../types.js'
4
4
  import { useField } from '../../hooks/useField.js'
5
+ import { annotateElement, isDevAnnotationsEnabled } from '../devAnnotations.js'
5
6
 
6
7
  /**
7
8
  * Field component - renders a scalar field value
@@ -28,13 +29,18 @@ function FieldImpl<T>({ field, children, format }: FieldProps<T>): ReactElement
28
29
 
29
30
  // useField() subscribes to store and returns FieldAccessor with .value access
30
31
  const accessor = useField(field)
32
+ const fieldName = field[FIELD_REF_META]?.fieldName
31
33
 
32
34
  if (children) {
33
- return <>{children(accessor)}</>
35
+ const result = children(accessor)
36
+ return <>{annotateElement(result, { 'data-field': fieldName })}</>
34
37
  }
35
38
 
36
39
  if (format) {
37
- return <>{format(accessor.value)}</>
40
+ const content = format(accessor.value)
41
+ return isDevAnnotationsEnabled() && fieldName
42
+ ? <span data-field={fieldName}>{content}</span>
43
+ : <>{content}</>
38
44
  }
39
45
 
40
46
  // Default: render value as string
@@ -42,7 +48,9 @@ function FieldImpl<T>({ field, children, format }: FieldProps<T>): ReactElement
42
48
  return null
43
49
  }
44
50
 
45
- return <>{String(accessor.value)}</>
51
+ return isDevAnnotationsEnabled() && fieldName
52
+ ? <span data-field={fieldName}>{String(accessor.value)}</span>
53
+ : <>{String(accessor.value)}</>
46
54
  }
47
55
 
48
56
  export const Field = memo(FieldImpl) as typeof FieldImpl
@@ -4,6 +4,7 @@ import { FIELD_REF_META, BINDX_COMPONENT, SCOPE_REF } from '../types.js'
4
4
  import { mergeSelections } from '../SelectionMeta.js'
5
5
  import { SelectionScope, generateHasManyAlias, type HasManyAccessor } from '@contember/bindx'
6
6
  import { useHasMany } from '../../hooks/useHasMany.js'
7
+ import { annotateElement } from '../devAnnotations.js'
7
8
 
8
9
  /**
9
10
  * HasMany component - renders a has-many relation
@@ -38,8 +39,16 @@ function HasManyImpl<
38
39
  >({ field, children }: HasManyProps<TEntity, TSelected, TBrand, TEntityName, TSchema>): ReactElement {
39
40
  // useHasMany() subscribes to store and returns HasManyAccessor with .map()/.items/.length
40
41
  const accessor = useHasMany(field)
42
+ const targetType = field[FIELD_REF_META]?.targetType
43
+
41
44
  const items = accessor.map((item, index) => {
42
- return <React.Fragment key={item.id}>{children(item, index)}</React.Fragment>
45
+ const result = children(item, index)
46
+ return <React.Fragment key={item.id}>{
47
+ annotateElement(result, {
48
+ 'data-entity': targetType ?? '',
49
+ 'data-entity-id': item.id,
50
+ })
51
+ }</React.Fragment>
43
52
  })
44
53
 
45
54
  return <>{items}</>
@@ -4,6 +4,7 @@ import { FIELD_REF_META, BINDX_COMPONENT, SCOPE_REF } from '../types.js'
4
4
  import { mergeSelections } from '../SelectionMeta.js'
5
5
  import { SelectionScope, type HasOneAccessor } from '@contember/bindx'
6
6
  import { useHasOne } from '../../hooks/useHasOne.js'
7
+ import { annotateElement } from '../devAnnotations.js'
7
8
 
8
9
  /**
9
10
  * HasOne component - renders a has-one relation
@@ -29,8 +30,11 @@ function HasOneImpl<
29
30
  >({ field, children }: HasOneProps<TEntity, TSelected, TBrand, TEntityName, TSchema>): ReactElement {
30
31
  // useHasOne() subscribes to store and returns HasOneAccessor with .$entity
31
32
  const accessor = useHasOne(field)
33
+ const relationName = field[FIELD_REF_META]?.fieldName
34
+
32
35
  // Get the related entity reference (always available, may be placeholder with id=null)
33
- return <>{children(accessor.$entity)}</>
36
+ const result = children(accessor.$entity)
37
+ return <>{annotateElement(result, { 'data-relation': relationName ?? '' })}</>
34
38
  }
35
39
 
36
40
  export const HasOne = memo(HasOneImpl) as typeof HasOneImpl
@@ -1,3 +1,6 @@
1
+ // Attribute component
2
+ export { Attribute, AttributeWithMeta, type AttributeProps } from './Attribute.js'
3
+
1
4
  // Field component
2
5
  export { Field, FieldWithMeta } from './Field.js'
3
6
 
@@ -0,0 +1,33 @@
1
+ import { cloneElement, isValidElement, type ReactNode } from 'react'
2
+
3
+ /**
4
+ * Whether dev-mode data attributes are enabled.
5
+ *
6
+ * Controlled via `BINDX_DEV_ANNOTATIONS` env variable.
7
+ * Bundlers (Vite, webpack) replace `import.meta.env` at build time,
8
+ * so this entire module tree-shakes away in production builds.
9
+ */
10
+ // @ts-ignore -- import.meta.env is provided by bundler
11
+ const DEV_ANNOTATIONS: boolean = typeof import.meta !== 'undefined' && import.meta.env?.VITE_BINDX_DEV_ANNOTATIONS === 'true'
12
+
13
+ /**
14
+ * In dev mode, injects data attributes onto an existing React element via cloneElement.
15
+ * If annotations are disabled or the node is not a valid element, returns as-is.
16
+ */
17
+ export function annotateElement(
18
+ element: ReactNode,
19
+ attrs: Record<string, string>,
20
+ ): ReactNode {
21
+ if (!DEV_ANNOTATIONS || !isValidElement(element)) {
22
+ return element
23
+ }
24
+ return cloneElement(element, attrs)
25
+ }
26
+
27
+ /**
28
+ * Returns true if dev annotations are enabled.
29
+ * Use for conditional wrapping (e.g., Field text render in span).
30
+ */
31
+ export function isDevAnnotationsEnabled(): boolean {
32
+ return DEV_ANNOTATIONS
33
+ }
package/src/jsx/index.ts CHANGED
@@ -52,6 +52,7 @@ export {
52
52
  } from './analyzer.js'
53
53
 
54
54
  // Components
55
+ export { Attribute, AttributeWithMeta, type AttributeProps } from './components/Attribute.js'
55
56
  export { Field, FieldWithMeta } from './components/Field.js'
56
57
  export { HasMany, HasManyWithMeta } from './components/HasMany.js'
57
58
  export { HasOne, HasOneWithMeta } from './components/HasOne.js'
package/src/jsx/types.ts CHANGED
@@ -97,7 +97,7 @@ export interface HasManyProps<
97
97
  TSchema extends Record<string, object> = Record<string, object>,
98
98
  > {
99
99
  field: HasManyRef<TEntity, TSelected, TBrand, TEntityName, TSchema>
100
- children: (item: EntityAccessor<TEntity, TSelected, TBrand, TEntityName, TSchema>, index: number) => ReactNode
100
+ children: (item: EntityRef<TEntity, TSelected, TBrand, TEntityName, TSchema>, index: number) => ReactNode
101
101
  filter?: unknown
102
102
  orderBy?: unknown
103
103
  limit?: number
@@ -123,7 +123,7 @@ export interface HasOneProps<
123
123
  TSchema extends Record<string, object> = Record<string, object>,
124
124
  > {
125
125
  field: HasOneRef<TEntity, TSelected, TBrand, TEntityName, TSchema>
126
- children: (entity: EntityAccessor<TEntity, TSelected, TBrand, TEntityName, TSchema>) => ReactNode
126
+ children: (entity: EntityRef<TEntity, TSelected, TBrand, TEntityName, TSchema>) => ReactNode
127
127
  }
128
128
 
129
129
  /**
@@ -134,7 +134,7 @@ export interface HasOneProps<
134
134
  export interface EntityComponentProps<TSchema extends Record<string, object>, K extends keyof TSchema & string> {
135
135
  name: K
136
136
  id: string
137
- children: (entity: EntityAccessor<TSchema[K], TSchema[K], import('@contember/bindx').AnyBrand, K, TSchema>) => ReactNode
137
+ children: (entity: EntityRef<TSchema[K], TSchema[K], import('@contember/bindx').AnyBrand, K, TSchema>) => ReactNode
138
138
  }
139
139
 
140
140
  /**