@teleporthq/teleport-uidl-resolver 0.43.53 → 0.43.57

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 (49) hide show
  1. package/__tests__/html-nesting/diagnostics.ts +95 -0
  2. package/__tests__/html-nesting/mocks.ts +66 -0
  3. package/__tests__/html-nesting/paragraph-repair.ts +251 -0
  4. package/dist/cjs/resolver.d.ts.map +1 -1
  5. package/dist/cjs/resolver.js +9 -0
  6. package/dist/cjs/resolver.js.map +1 -1
  7. package/dist/cjs/resolvers/html-nesting/diagnostics.d.ts +23 -0
  8. package/dist/cjs/resolvers/html-nesting/diagnostics.d.ts.map +1 -0
  9. package/dist/cjs/resolvers/html-nesting/diagnostics.js +77 -0
  10. package/dist/cjs/resolvers/html-nesting/diagnostics.js.map +1 -0
  11. package/dist/cjs/resolvers/html-nesting/index.d.ts +33 -0
  12. package/dist/cjs/resolvers/html-nesting/index.d.ts.map +1 -0
  13. package/dist/cjs/resolvers/html-nesting/index.js +185 -0
  14. package/dist/cjs/resolvers/html-nesting/index.js.map +1 -0
  15. package/dist/cjs/resolvers/html-nesting/paragraph-repair.d.ts +15 -0
  16. package/dist/cjs/resolvers/html-nesting/paragraph-repair.d.ts.map +1 -0
  17. package/dist/cjs/resolvers/html-nesting/paragraph-repair.js +129 -0
  18. package/dist/cjs/resolvers/html-nesting/paragraph-repair.js.map +1 -0
  19. package/dist/cjs/resolvers/html-nesting/types.d.ts +37 -0
  20. package/dist/cjs/resolvers/html-nesting/types.d.ts.map +1 -0
  21. package/dist/cjs/resolvers/html-nesting/types.js +3 -0
  22. package/dist/cjs/resolvers/html-nesting/types.js.map +1 -0
  23. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  24. package/dist/esm/resolver.d.ts.map +1 -1
  25. package/dist/esm/resolver.js +9 -0
  26. package/dist/esm/resolver.js.map +1 -1
  27. package/dist/esm/resolvers/html-nesting/diagnostics.d.ts +23 -0
  28. package/dist/esm/resolvers/html-nesting/diagnostics.d.ts.map +1 -0
  29. package/dist/esm/resolvers/html-nesting/diagnostics.js +72 -0
  30. package/dist/esm/resolvers/html-nesting/diagnostics.js.map +1 -0
  31. package/dist/esm/resolvers/html-nesting/index.d.ts +33 -0
  32. package/dist/esm/resolvers/html-nesting/index.d.ts.map +1 -0
  33. package/dist/esm/resolvers/html-nesting/index.js +181 -0
  34. package/dist/esm/resolvers/html-nesting/index.js.map +1 -0
  35. package/dist/esm/resolvers/html-nesting/paragraph-repair.d.ts +15 -0
  36. package/dist/esm/resolvers/html-nesting/paragraph-repair.d.ts.map +1 -0
  37. package/dist/esm/resolvers/html-nesting/paragraph-repair.js +123 -0
  38. package/dist/esm/resolvers/html-nesting/paragraph-repair.js.map +1 -0
  39. package/dist/esm/resolvers/html-nesting/types.d.ts +37 -0
  40. package/dist/esm/resolvers/html-nesting/types.d.ts.map +1 -0
  41. package/dist/esm/resolvers/html-nesting/types.js +2 -0
  42. package/dist/esm/resolvers/html-nesting/types.js.map +1 -0
  43. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +5 -5
  45. package/src/resolver.ts +10 -0
  46. package/src/resolvers/html-nesting/diagnostics.ts +88 -0
  47. package/src/resolvers/html-nesting/index.ts +253 -0
  48. package/src/resolvers/html-nesting/paragraph-repair.ts +152 -0
  49. package/src/resolvers/html-nesting/types.ts +39 -0
@@ -0,0 +1,95 @@
1
+ import { GeneratorOptions } from '@teleporthq/teleport-types'
2
+ import { resolveHtmlNesting } from '../../src/resolvers/html-nesting'
3
+ import { component, element, text } from './mocks'
4
+
5
+ const NO_OPTIONS: GeneratorOptions = {}
6
+
7
+ let warnSpy: jest.SpyInstance
8
+
9
+ const warnings = (): string[] => warnSpy.mock.calls.map((call) => String(call[0]))
10
+
11
+ beforeEach(() => {
12
+ warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined)
13
+ })
14
+
15
+ afterEach(() => {
16
+ warnSpy.mockRestore()
17
+ })
18
+
19
+ describe('resolveHtmlNesting — nesting that has no lossless repair', () => {
20
+ it('reports a nested anchor and leaves both in place', () => {
21
+ const inner = element('a', [text('Details')])
22
+ const outer = element('a', [element('div', [inner])])
23
+
24
+ resolveHtmlNesting(component(outer, 'Product-Card'), NO_OPTIONS)
25
+
26
+ expect(inner.content.elementType).toBe('a')
27
+ expect(warnings()).toEqual([expect.stringContaining('<a> nested inside another <a>')])
28
+ expect(warnings()[0]).toContain('Product-Card')
29
+ })
30
+
31
+ it('forgets an anchor once a scope boundary hides it from the parser', () => {
32
+ const inner = element('a', [text('Details')])
33
+ const outer = element('a', [element('table', [element('td', [inner])])])
34
+
35
+ resolveHtmlNesting(component(outer), NO_OPTIONS)
36
+
37
+ expect(warnings().filter((message) => message.includes('nested inside another <a>'))).toEqual(
38
+ []
39
+ )
40
+ })
41
+
42
+ it('reports a nested form', () => {
43
+ const outer = element('form', [element('div', [element('form', [])])])
44
+
45
+ resolveHtmlNesting(component(outer), NO_OPTIONS)
46
+
47
+ expect(warnings()).toEqual([expect.stringContaining('<form> nested inside another <form>')])
48
+ })
49
+
50
+ it('reports content the parser would hoist out of a table', () => {
51
+ const table = element('table', [element('div', [])])
52
+
53
+ resolveHtmlNesting(component(table), NO_OPTIONS)
54
+
55
+ expect(warnings()).toEqual([expect.stringContaining('<div> is not valid content for <table>')])
56
+ })
57
+
58
+ it('sees through a component wrapper when checking table content', () => {
59
+ const table = element('table', [element('Repeater', [element('tr', [])])])
60
+
61
+ resolveHtmlNesting(component(table), NO_OPTIONS)
62
+
63
+ expect(warnings()).toEqual([expect.stringContaining('<tr> is not valid content for <table>')])
64
+ })
65
+
66
+ it('accepts a well-formed table', () => {
67
+ const table = element('table', [
68
+ element('tbody', [element('tr', [element('td', [text('7')])])]),
69
+ ])
70
+
71
+ resolveHtmlNesting(component(table), NO_OPTIONS)
72
+
73
+ expect(warnings()).toEqual([])
74
+ })
75
+
76
+ it('reports a heading inside a heading', () => {
77
+ const heading = element('h2', [element('h3', [text('Sub')])])
78
+
79
+ resolveHtmlNesting(component(heading), NO_OPTIONS)
80
+
81
+ expect(warnings()).toEqual([expect.stringContaining('<h3> nested inside <h2>')])
82
+ })
83
+
84
+ it('says nothing about markup that parses as written', () => {
85
+ const section = element('section', [
86
+ element('h2', [text('Title')]),
87
+ element('p', [element('a', [element('span', [text('link')])])]),
88
+ element('button', [element('span', [text('Buy')])]),
89
+ ])
90
+
91
+ resolveHtmlNesting(component(section), NO_OPTIONS)
92
+
93
+ expect(warnings()).toEqual([])
94
+ })
95
+ })
@@ -0,0 +1,66 @@
1
+ import {
2
+ ComponentUIDL,
3
+ GeneratorOptions,
4
+ UIDLElementNode,
5
+ UIDLNode,
6
+ UIDLStyleSetDefinition,
7
+ UIDLStyleSheetContent,
8
+ } from '@teleporthq/teleport-types'
9
+
10
+ /**
11
+ * The nesting resolver runs on an ALREADY MAPPED tree, so these mocks are
12
+ * written with the final tag in `elementType` — exactly the shape
13
+ * `utils.resolveNode` leaves behind.
14
+ */
15
+ export const element = (
16
+ elementType: string,
17
+ children: UIDLNode[] = [],
18
+ content: Partial<UIDLElementNode['content']> = {}
19
+ ): UIDLElementNode => ({
20
+ type: 'element',
21
+ content: {
22
+ elementType,
23
+ children,
24
+ ...content,
25
+ },
26
+ })
27
+
28
+ export const text = (content: string): UIDLNode => ({ type: 'static', content })
29
+
30
+ export const component = (node: UIDLElementNode, name = 'Homepage'): ComponentUIDL =>
31
+ ({ name, node } as ComponentUIDL)
32
+
33
+ /** A node-level `referencedStyles` entry pointing at a project style set. */
34
+ export const referencingProjectStyle = (
35
+ referenceId: string
36
+ ): Partial<UIDLElementNode['content']> => ({
37
+ referencedStyles: {
38
+ [referenceId]: {
39
+ type: 'style-map',
40
+ content: { mapType: 'project-referenced', referenceId },
41
+ },
42
+ },
43
+ })
44
+
45
+ export const optionsWithProjectStyles = (
46
+ styleSetDefinitions: Record<string, UIDLStyleSetDefinition>
47
+ ): GeneratorOptions => ({
48
+ projectStyleSet: { styleSetDefinitions, fileName: 'style', path: '.' },
49
+ })
50
+
51
+ export const staticStyleSet = (declarations: Record<string, string>): UIDLStyleSetDefinition => {
52
+ const content: Record<string, UIDLStyleSheetContent> = {}
53
+ Object.keys(declarations).forEach((property) => {
54
+ content[property] = { type: 'static', content: declarations[property] }
55
+ })
56
+ return { type: 'reusable-project-style-map', content }
57
+ }
58
+
59
+ /** The `elementType` of every element in the tree, in depth-first order. */
60
+ export const tagsOf = (node: UIDLNode, tags: string[] = []): string[] => {
61
+ if (node.type === 'element') {
62
+ tags.push(node.content.elementType)
63
+ ;(node.content.children || []).forEach((child) => tagsOf(child, tags))
64
+ }
65
+ return tags
66
+ }
@@ -0,0 +1,251 @@
1
+ import { GeneratorOptions, UIDLElementNode } from '@teleporthq/teleport-types'
2
+ import { resolveHtmlNesting } from '../../src/resolvers/html-nesting'
3
+ import {
4
+ component,
5
+ element,
6
+ optionsWithProjectStyles,
7
+ referencingProjectStyle,
8
+ staticStyleSet,
9
+ tagsOf,
10
+ text,
11
+ } from './mocks'
12
+
13
+ const NO_OPTIONS: GeneratorOptions = {}
14
+
15
+ // `console.warn` is the resolver's diagnostic channel; silence it so the suite
16
+ // output stays readable while still exercising the code path.
17
+ let warnSpy: jest.SpyInstance
18
+
19
+ beforeEach(() => {
20
+ warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined)
21
+ })
22
+
23
+ afterEach(() => {
24
+ warnSpy.mockRestore()
25
+ })
26
+
27
+ describe('resolveHtmlNesting — paragraph content', () => {
28
+ it('retags a wrapper <div> reached through a framework component', () => {
29
+ // The shape that shipped: <p>… <Link><a><div><span>Privacy Policy</span>.
30
+ const wrapper = element('div', [element('span', [text('Privacy Policy')])])
31
+ const paragraph = element('p', [
32
+ element('span', [text('We promise only joy, no junk. See our ')]),
33
+ element('Link', [element('a', [wrapper])]),
34
+ element('span', [text('.')]),
35
+ ])
36
+
37
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
38
+
39
+ expect(paragraph.content.elementType).toBe('p')
40
+ expect(wrapper.content.elementType).toBe('span')
41
+ expect(tagsOf(paragraph)).toEqual(['p', 'span', 'Link', 'a', 'span', 'span', 'span'])
42
+ })
43
+
44
+ it('restates the block display the retagged wrapper loses with its tag', () => {
45
+ const wrapper = element('div', [element('span', [text('Read more')])])
46
+ const paragraph = element('p', [wrapper])
47
+
48
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
49
+
50
+ expect(wrapper.content.style).toEqual({ display: { type: 'static', content: 'block' } })
51
+ })
52
+
53
+ it('leaves the display alone when the node already declares one', () => {
54
+ const wrapper = element('div', [], {
55
+ style: { display: { type: 'static', content: 'flex' } },
56
+ })
57
+ const paragraph = element('p', [wrapper])
58
+
59
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
60
+
61
+ expect(wrapper.content.elementType).toBe('span')
62
+ expect(wrapper.content.style.display).toEqual({ type: 'static', content: 'flex' })
63
+ })
64
+
65
+ it('leaves the display alone when a project class declares one', () => {
66
+ const wrapper = element('div', [], referencingProjectStyle('card-row'))
67
+ const paragraph = element('p', [wrapper])
68
+
69
+ resolveHtmlNesting(
70
+ component(paragraph),
71
+ optionsWithProjectStyles({ 'card-row': staticStyleSet({ display: 'flex' }) })
72
+ )
73
+
74
+ expect(wrapper.content.elementType).toBe('span')
75
+ expect(wrapper.content.style).toBeUndefined()
76
+ })
77
+
78
+ it('adds the display when the project class it references declares none', () => {
79
+ const wrapper = element('div', [], referencingProjectStyle('card-row'))
80
+ const paragraph = element('p', [wrapper])
81
+
82
+ resolveHtmlNesting(
83
+ component(paragraph),
84
+ optionsWithProjectStyles({ 'card-row': staticStyleSet({ color: 'red' }) })
85
+ )
86
+
87
+ expect(wrapper.content.style).toEqual({ display: { type: 'static', content: 'block' } })
88
+ })
89
+
90
+ it('does not guess a display when the referenced style set cannot be read', () => {
91
+ const wrapper = element('div', [], referencingProjectStyle('card-row'))
92
+ const paragraph = element('p', [wrapper])
93
+
94
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
95
+
96
+ expect(wrapper.content.elementType).toBe('span')
97
+ expect(wrapper.content.style).toBeUndefined()
98
+ })
99
+
100
+ it('retags every offending wrapper, however deep', () => {
101
+ const outer = element('div', [])
102
+ const inner = element('div', [])
103
+ outer.content.children = [element('span', [inner])]
104
+ const paragraph = element('p', [outer])
105
+
106
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
107
+
108
+ expect(outer.content.elementType).toBe('span')
109
+ expect(inner.content.elementType).toBe('span')
110
+ })
111
+
112
+ it('demotes the paragraph when an offender carries meaning of its own', () => {
113
+ const list = element('ul', [element('li', [text('One')])])
114
+ const paragraph = element('p', [element('span', [text('Sizes:')]), list], {
115
+ semanticType: 'p',
116
+ })
117
+
118
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
119
+
120
+ expect(paragraph.content.elementType).toBe('div')
121
+ expect(paragraph.content.semanticType).toBe('div')
122
+ expect(list.content.elementType).toBe('ul')
123
+ })
124
+
125
+ it('restores the paragraph block margins the demotion drops', () => {
126
+ const paragraph = element('p', [element('h3', [text('Heading')])])
127
+
128
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
129
+
130
+ expect(paragraph.content.style).toEqual({
131
+ marginTop: { type: 'static', content: '1em' },
132
+ marginBottom: { type: 'static', content: '1em' },
133
+ })
134
+ })
135
+
136
+ it('keeps the authored spacing instead of restoring the browser default', () => {
137
+ const paragraph = element('p', [element('h3', [text('Heading')])], {
138
+ ...referencingProjectStyle('newsletter-privacy'),
139
+ })
140
+
141
+ resolveHtmlNesting(
142
+ component(paragraph),
143
+ optionsWithProjectStyles({ 'newsletter-privacy': staticStyleSet({ margin: '0' }) })
144
+ )
145
+
146
+ expect(paragraph.content.elementType).toBe('div')
147
+ expect(paragraph.content.style).toBeUndefined()
148
+ })
149
+
150
+ it('demotes rather than retags when the paragraph holds both kinds of offender', () => {
151
+ const wrapper = element('div', [])
152
+ const paragraph = element('p', [wrapper, element('table', [])])
153
+
154
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
155
+
156
+ expect(paragraph.content.elementType).toBe('div')
157
+ expect(wrapper.content.elementType).toBe('div')
158
+ })
159
+
160
+ it('repairs a nested paragraph on its own terms', () => {
161
+ const wrapper = element('div', [])
162
+ const inner = element('p', [wrapper])
163
+ const outer = element('p', [inner])
164
+
165
+ resolveHtmlNesting(component(outer), NO_OPTIONS)
166
+
167
+ expect(outer.content.elementType).toBe('div')
168
+ expect(inner.content.elementType).toBe('p')
169
+ expect(wrapper.content.elementType).toBe('span')
170
+ })
171
+
172
+ it('leaves a paragraph whose content is already valid untouched', () => {
173
+ const paragraph = element('p', [
174
+ element('span', [text('See our ')]),
175
+ element('a', [element('span', [text('terms')])]),
176
+ ])
177
+
178
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
179
+
180
+ expect(tagsOf(paragraph)).toEqual(['p', 'span', 'a', 'span'])
181
+ expect(warnSpy).not.toHaveBeenCalled()
182
+ })
183
+
184
+ it('ignores a wrapper that a <button> puts out of paragraph scope', () => {
185
+ // `<p><button><div/></button></p>` parses as written — the parser only
186
+ // closes a paragraph that is in BUTTON scope.
187
+ const wrapper = element('div', [])
188
+ const paragraph = element('p', [element('button', [wrapper])])
189
+
190
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
191
+
192
+ expect(paragraph.content.elementType).toBe('p')
193
+ expect(wrapper.content.elementType).toBe('div')
194
+ })
195
+
196
+ it('does not descend into <svg>, where the HTML content model does not apply', () => {
197
+ const svgChild = element('title', [text('Arrow')])
198
+ const paragraph = element('p', [element('svg', [svgChild])])
199
+
200
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
201
+
202
+ expect(paragraph.content.elementType).toBe('p')
203
+ expect(svgChild.content.elementType).toBe('title')
204
+ expect(warnSpy).not.toHaveBeenCalled()
205
+ })
206
+
207
+ it('repairs render branches of a CMS repeater against the enclosing paragraph', () => {
208
+ const listWrapper = element('div', [])
209
+ const emptyWrapper = element('div', [])
210
+ const paragraph: UIDLElementNode = element('p', [
211
+ {
212
+ type: 'cms-list-repeater',
213
+ content: {
214
+ nodes: { list: listWrapper, empty: emptyWrapper },
215
+ },
216
+ } as never,
217
+ ])
218
+
219
+ resolveHtmlNesting(component(paragraph), NO_OPTIONS)
220
+
221
+ expect(listWrapper.content.elementType).toBe('span')
222
+ expect(emptyWrapper.content.elementType).toBe('span')
223
+ })
224
+
225
+ it('inspects an element passed as an attribute on its own', () => {
226
+ const wrapper = element('div', [])
227
+ const slotParagraph = element('p', [wrapper])
228
+ const host = element('p', [], { attrs: { renderItem: slotParagraph } })
229
+
230
+ resolveHtmlNesting(component(host), NO_OPTIONS)
231
+
232
+ // The attribute tree is repaired against ITS OWN paragraph, and the host
233
+ // paragraph is not blamed for content it never renders inline.
234
+ expect(wrapper.content.elementType).toBe('span')
235
+ expect(slotParagraph.content.elementType).toBe('p')
236
+ expect(host.content.elementType).toBe('p')
237
+ })
238
+
239
+ it('walks element trees held in prop definitions', () => {
240
+ const wrapper = element('div', [])
241
+ const paragraph = element('p', [wrapper])
242
+ const uidl = component(element('div', []))
243
+ uidl.propDefinitions = {
244
+ header: { type: 'element', defaultValue: paragraph },
245
+ } as never
246
+
247
+ resolveHtmlNesting(uidl, NO_OPTIONS)
248
+
249
+ expect(wrapper.content.elementType).toBe('span')
250
+ })
251
+ })
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,EACP,gBAAgB,EAEjB,MAAM,4BAA4B,CAAA;AAOnC;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC3B,OAAO,CAAC,OAAO,CAMd;gBAEW,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE;IAQlC,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,gBAA6C;IAyCxF,cAAc,CACnB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,gBAA6C;CAYzD"}
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,EACP,gBAAgB,EAEjB,MAAM,4BAA4B,CAAA;AAQnC;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC3B,OAAO,CAAC,OAAO,CAMd;gBAEW,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE;IAQlC,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,gBAA6C;IAkDxF,cAAc,CACnB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,gBAA6C;CAYzD"}
@@ -41,6 +41,7 @@ var style_set_definitions_1 = require("./resolvers/style-set-definitions");
41
41
  var referenced_styles_1 = require("./resolvers/referenced-styles");
42
42
  var embed_node_1 = require("./resolvers/embed-node");
43
43
  var unbound_expressions_1 = require("./resolvers/unbound-expressions");
44
+ var html_nesting_1 = require("./resolvers/html-nesting");
44
45
  /**
45
46
  * The resolver takes the input UIDL and converts all the abstract node types into
46
47
  * concrete node types, based on the mappings you provide
@@ -81,6 +82,14 @@ var Resolver = /** @class */ (function () {
81
82
  (0, embed_node_1.resolveHtmlNode)(uidl, newOptions);
82
83
  utils.resolveNode(uidl.node, newOptions);
83
84
  utils.resolveNodeInPropDefinitions(uidl, newOptions);
85
+ // Element types are concrete HTML tags from here on, which is the earliest
86
+ // point at which the markup can be checked against the HTML content model.
87
+ // Nesting the parser refuses to keep (a `<div>` inside a `<p>`, a `<form>`
88
+ // inside a `<form>`) makes the served HTML parse into a different tree than
89
+ // the one that was rendered, and React then fails to hydrate the page.
90
+ if (!newOptions.skipHtmlNestingResolver) {
91
+ (0, html_nesting_1.resolveHtmlNesting)(uidl, newOptions);
92
+ }
84
93
  // Guard against `expr` nodes that reference an out-of-scope identifier
85
94
  // (e.g. an orphaned `<option>{{ cat.name }}</option>` whose repeater was
86
95
  // never generated). Left untouched they compile to `ReferenceError`s that
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { UIDLElementNode } from '@teleporthq/teleport-types';
2
+ import { NestingContext, NestingResolverContext } from './types';
3
+ /**
4
+ * The DOM-restructuring nestings that have no lossless repair.
5
+ *
6
+ * Every one of them either removes an element the page needs (`<form>` inside
7
+ * `<form>` — the inner one is never created) or moves it somewhere else
8
+ * (`<div>` inside `<tbody>` is foster-parented out of the table). Renaming a
9
+ * tag to make the markup parse would silently drop the behaviour the element
10
+ * was added for, so these are reported rather than rewritten: the fix belongs
11
+ * in whatever produced the tree.
12
+ *
13
+ * Paragraph content is deliberately absent — it IS repairable losslessly, and
14
+ * `paragraph-repair` handles it.
15
+ */
16
+ export declare const reportUnrepairableNesting: (node: UIDLElementNode, tag: string, nesting: NestingContext, context: NestingResolverContext) => void;
17
+ /**
18
+ * `ancestorInfo` bookkeeping for the tags whose duplicates the parser rejects.
19
+ * `<a>`, `<button>` and `<nobr>` stop counting past a scope boundary (a table
20
+ * cell, an `<object>`, …); the form pointer has no such boundary.
21
+ */
22
+ export declare const nextOpenSelfNestingTags: (tag: string, current: Record<string, boolean>) => Record<string, boolean>;
23
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../../src/resolvers/html-nesting/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAE5D,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAmBhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,SAC9B,eAAe,OAChB,MAAM,WACF,cAAc,WACd,sBAAsB,KAC9B,IA0BF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAC7B,MAAM,WACF,OAAO,MAAM,EAAE,OAAO,CAAC,KAC/B,OAAO,MAAM,EAAE,OAAO,CAYxB,CAAA"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.nextOpenSelfNestingTags = exports.reportUnrepairableNesting = void 0;
15
+ var teleport_shared_1 = require("@teleporthq/teleport-shared");
16
+ var SELF_NESTING_TAGS = teleport_shared_1.HtmlContentModel.SELF_NESTING_TAGS, acceptsChildTag = teleport_shared_1.HtmlContentModel.acceptsChildTag, isHeadingTag = teleport_shared_1.HtmlContentModel.isHeadingTag, endsInlineScope = teleport_shared_1.HtmlContentModel.endsInlineScope;
17
+ var HYDRATION_DOC = 'nextjs.org/docs/messages/react-hydration-error';
18
+ var warn = function (context, message) {
19
+ console.warn("[html-nesting] ".concat(context.componentName, ": ").concat(message, " (see ").concat(HYDRATION_DOC, ")"));
20
+ };
21
+ /**
22
+ * `<div>` alone rarely tells anyone which node to open. The UIDL `name` is the
23
+ * element's identity in the editor, so it is quoted whenever it adds anything.
24
+ */
25
+ var describeElement = function (node, tag) {
26
+ var name = node.content.name;
27
+ return name && name !== tag ? "<".concat(tag, "> \"").concat(name, "\"") : "<".concat(tag, ">");
28
+ };
29
+ /**
30
+ * The DOM-restructuring nestings that have no lossless repair.
31
+ *
32
+ * Every one of them either removes an element the page needs (`<form>` inside
33
+ * `<form>` — the inner one is never created) or moves it somewhere else
34
+ * (`<div>` inside `<tbody>` is foster-parented out of the table). Renaming a
35
+ * tag to make the markup parse would silently drop the behaviour the element
36
+ * was added for, so these are reported rather than rewritten: the fix belongs
37
+ * in whatever produced the tree.
38
+ *
39
+ * Paragraph content is deliberately absent — it IS repairable losslessly, and
40
+ * `paragraph-repair` handles it.
41
+ */
42
+ var reportUnrepairableNesting = function (node, tag, nesting, context) {
43
+ var element = describeElement(node, tag);
44
+ if (SELF_NESTING_TAGS.has(tag) && nesting.openSelfNestingTags[tag]) {
45
+ warn(context, "".concat(element, " nested inside another <").concat(tag, ">. The parser refuses to nest them and ") +
46
+ "closes or drops one of the two");
47
+ }
48
+ var parentTag = nesting.parentTag;
49
+ if (!parentTag) {
50
+ return;
51
+ }
52
+ if (!acceptsChildTag(parentTag, tag)) {
53
+ warn(context, "".concat(element, " is not valid content for <").concat(parentTag, ">. The parser moves or drops it"));
54
+ }
55
+ if (isHeadingTag(tag) && isHeadingTag(parentTag)) {
56
+ warn(context, "".concat(element, " nested inside <").concat(parentTag, ">. The parser closes the outer heading"));
57
+ }
58
+ };
59
+ exports.reportUnrepairableNesting = reportUnrepairableNesting;
60
+ /**
61
+ * `ancestorInfo` bookkeeping for the tags whose duplicates the parser rejects.
62
+ * `<a>`, `<button>` and `<nobr>` stop counting past a scope boundary (a table
63
+ * cell, an `<object>`, …); the form pointer has no such boundary.
64
+ */
65
+ var nextOpenSelfNestingTags = function (tag, current) {
66
+ var _a;
67
+ var next = current;
68
+ if (endsInlineScope(tag)) {
69
+ next = __assign(__assign({}, next), { a: false, button: false, nobr: false });
70
+ }
71
+ if (SELF_NESTING_TAGS.has(tag)) {
72
+ next = __assign(__assign({}, next), (_a = {}, _a[tag] = true, _a));
73
+ }
74
+ return next;
75
+ };
76
+ exports.nextOpenSelfNestingTags = nextOpenSelfNestingTags;
77
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../../../src/resolvers/html-nesting/diagnostics.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AACA,+DAA8D;AAGtD,IAAA,iBAAiB,GAAqD,kCAAgB,kBAArE,EAAE,eAAe,GAAoC,kCAAgB,gBAApD,EAAE,YAAY,GAAsB,kCAAgB,aAAtC,EAAE,eAAe,GAAK,kCAAgB,gBAArB,CAAqB;AAE9F,IAAM,aAAa,GAAG,gDAAgD,CAAA;AAEtE,IAAM,IAAI,GAAG,UAAC,OAA+B,EAAE,OAAe;IAC5D,OAAO,CAAC,IAAI,CAAC,yBAAkB,OAAO,CAAC,aAAa,eAAK,OAAO,mBAAS,aAAa,MAAG,CAAC,CAAA;AAC5F,CAAC,CAAA;AAED;;;GAGG;AACH,IAAM,eAAe,GAAG,UAAC,IAAqB,EAAE,GAAW;IACjD,IAAA,IAAI,GAAK,IAAI,CAAC,OAAO,KAAjB,CAAiB;IAC7B,OAAO,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,WAAI,GAAG,iBAAM,IAAI,OAAG,CAAC,CAAC,CAAC,WAAI,GAAG,MAAG,CAAA;AACjE,CAAC,CAAA;AAED;;;;;;;;;;;;GAYG;AACI,IAAM,yBAAyB,GAAG,UACvC,IAAqB,EACrB,GAAW,EACX,OAAuB,EACvB,OAA+B;IAE/B,IAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAE1C,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE;QAClE,IAAI,CACF,OAAO,EACP,UAAG,OAAO,qCAA2B,GAAG,4CAAyC;YAC/E,gCAAgC,CACnC,CAAA;KACF;IAEO,IAAA,SAAS,GAAK,OAAO,UAAZ,CAAY;IAC7B,IAAI,CAAC,SAAS,EAAE;QACd,OAAM;KACP;IAED,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE;QACpC,IAAI,CACF,OAAO,EACP,UAAG,OAAO,wCAA8B,SAAS,oCAAiC,CACnF,CAAA;KACF;IAED,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,SAAS,CAAC,EAAE;QAChD,IAAI,CAAC,OAAO,EAAE,UAAG,OAAO,6BAAmB,SAAS,2CAAwC,CAAC,CAAA;KAC9F;AACH,CAAC,CAAA;AA/BY,QAAA,yBAAyB,6BA+BrC;AAED;;;;GAIG;AACI,IAAM,uBAAuB,GAAG,UACrC,GAAW,EACX,OAAgC;;IAEhC,IAAI,IAAI,GAAG,OAAO,CAAA;IAElB,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE;QACxB,IAAI,yBAAQ,IAAI,KAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,GAAE,CAAA;KACzD;IAED,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC9B,IAAI,yBAAQ,IAAI,gBAAG,GAAG,IAAG,IAAI,MAAE,CAAA;KAChC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAfY,QAAA,uBAAuB,2BAenC"}
@@ -0,0 +1,33 @@
1
+ import { ComponentUIDL, GeneratorOptions } from '@teleporthq/teleport-types';
2
+ /**
3
+ * Makes the generated markup parse into the tree it was rendered as.
4
+ *
5
+ * Server-rendered HTML is re-parsed by the browser before React hydrates it,
6
+ * and a handful of tag combinations make the parser rewrite the tree — closing
7
+ * a `<p>` early, hoisting a `<div>` out of a `<table>`, dropping a nested
8
+ * `<form>`. Whatever React rendered on the server no longer matches the DOM it
9
+ * finds on the client, hydration fails, and the page is silently re-rendered
10
+ * from scratch: styles flash, server-computed content is thrown away, and the
11
+ * console fills with "Hydration failed because the initial UI does not match
12
+ * what was rendered on the server".
13
+ *
14
+ * The pass runs after the element mapping, so every `elementType` is already
15
+ * the tag that will be emitted (`text`+`p` has become `p`, a navlink has become
16
+ * `Link` wrapping an `a`). It walks the resolved tree with the same
17
+ * `ancestorInfo` bookkeeping react-dom's `validateDOMNesting` uses, so it flags
18
+ * exactly what React would have flagged at runtime — only early enough to do
19
+ * something about it.
20
+ *
21
+ * Paragraph content is repaired in place, because a rename is enough and costs
22
+ * nothing (see `paragraph-repair`). The rest is reported: renaming a `<form>`
23
+ * or a `<td>` to make it parse would throw away the behaviour it was added for.
24
+ *
25
+ * Element types that are not HTML tags — `Link`, `Repeater`, `Fragment`,
26
+ * `TqMotion`, custom elements — have no content model of their own. They are
27
+ * walked THROUGH rather than treated as a boundary: `<p><Link><a><div>` is the
28
+ * shape that shipped, and only a walk that ignores the component in the middle
29
+ * can see it. Component INSTANCES are a real boundary and are not walked at
30
+ * all; each one is resolved as its own UIDL.
31
+ */
32
+ export declare const resolveHtmlNesting: (uidl: ComponentUIDL, options: GeneratorOptions) => void;
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/resolvers/html-nesting/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,gBAAgB,EAGjB,MAAM,4BAA4B,CAAA;AAkBnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,kBAAkB,SAAU,aAAa,WAAW,gBAAgB,KAAG,IAiBnF,CAAA"}