diff --git a/.changeset/purple-chairs-relax.md b/.changeset/purple-chairs-relax.md new file mode 100644 index 000000000..dbdeac389 --- /dev/null +++ b/.changeset/purple-chairs-relax.md @@ -0,0 +1,5 @@ +--- +'@astrojs/compiler': minor +--- + +Support the transition:persist directive diff --git a/cmd/astro-wasm/astro-wasm.go b/cmd/astro-wasm/astro-wasm.go index b400e9eb7..1b08f2364 100644 --- a/cmd/astro-wasm/astro-wasm.go +++ b/cmd/astro-wasm/astro-wasm.go @@ -99,6 +99,10 @@ func makeTransformOptions(options js.Value) transform.TransformOptions { if jsBool(options.Get("experimentalTransitions")) { experimentalTransitions = true } + experimentalPersistence := false + if jsBool(options.Get("experimentalPersistence")) { + experimentalPersistence = true + } transitionsAnimationURL := jsString(options.Get("transitionsAnimationURL")) if transitionsAnimationURL == "" { transitionsAnimationURL = "astro/components/viewtransitions.css" @@ -136,6 +140,7 @@ func makeTransformOptions(options js.Value) transform.TransformOptions { ResultScopedSlot: scopedSlot, ScopedStyleStrategy: scopedStyleStrategy, ExperimentalTransitions: experimentalTransitions, + ExperimentalPersistence: experimentalPersistence, TransitionsAnimationURL: transitionsAnimationURL, } } diff --git a/internal/printer/printer.go b/internal/printer/printer.go index 9c2c472bd..d6436e708 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -42,6 +42,7 @@ var RENDER_SLOT = "$$renderSlot" var MERGE_SLOTS = "$$mergeSlots" var ADD_ATTRIBUTE = "$$addAttribute" var RENDER_TRANSITION = "$$renderTransition" +var CREATE_TRANSITION_SCOPE = "$$createTransitionScope" var SPREAD_ATTRIBUTES = "$$spreadAttributes" var DEFINE_STYLE_VARS = "$$defineStyleVars" var DEFINE_SCRIPT_VARS = "$$defineScriptVars" @@ -122,6 +123,10 @@ func (p *printer) printInternalImports(importSpecifier string, opts *RenderOptio p.addNilSourceMapping() p.print("renderTransition as " + RENDER_TRANSITION + ",\n ") } + if opts.opts.ExperimentalPersistence { + p.addNilSourceMapping() + p.print("createTransitionScope as " + CREATE_TRANSITION_SCOPE + ",\n ") + } // Only needed if using fallback `resolvePath` as it calls `$$metadata.resolvePath` if opts.opts.ResolvePath == nil { @@ -276,6 +281,25 @@ func (p *printer) printFuncSuffix(opts transform.TransformOptions, n *astro.Node p.println(fmt.Sprintf("export default %s;", componentName)) } +var skippedAttributes = map[string]bool{ + "define:vars": true, + "set:text": true, + "set:html": true, + "is:raw": true, + "transition:animate": true, + "transition:name": true, + "transition:persist": true, +} + +var skippedAttributesToObject = map[string]bool{ + "set:text": true, + "set:html": true, + "is:raw": true, + "transition:animate": true, + "transition:name": true, + "transition:persist": true, +} + func (p *printer) printAttributesToObject(n *astro.Node) { lastAttributeSkipped := false p.print("{") @@ -283,7 +307,7 @@ func (p *printer) printAttributesToObject(n *astro.Node) { if i != 0 && !lastAttributeSkipped { p.print(",") } - if a.Key == "set:text" || a.Key == "set:html" || a.Key == "is:raw" || a.Key == "transition:animate" || a.Key == "transition:name" { + if _, ok := skippedAttributesToObject[a.Key]; ok { lastAttributeSkipped = true continue } @@ -338,7 +362,7 @@ func (p *printer) printAttributesToObject(n *astro.Node) { } func (p *printer) printAttribute(attr astro.Attribute, n *astro.Node) { - if attr.Key == "define:vars" || attr.Key == "set:text" || attr.Key == "set:html" || attr.Key == "is:raw" || attr.Key == "transition:animate" || attr.Key == "transition:name" { + if _, ok := skippedAttributes[attr.Key]; ok { return } @@ -458,6 +482,26 @@ func maybeConvertTransition(n *astro.Node) { Type: astro.ExpressionAttribute, }) } + if transform.HasAttr(n, transform.TRANSITION_PERSIST) { + transitionPersistIndex := transform.AttrIndex(n, transform.TRANSITION_PERSIST) + // If there no value, create a transition scope for this element + if n.Attr[transitionPersistIndex].Val != "" { + // Just rename the attribute + n.Attr[transitionPersistIndex].Key = "data-astro-transition-persist" + + } else if transform.HasAttr(n, transform.TRANSITION_NAME) { + transitionNameAttr := transform.GetAttr(n, transform.TRANSITION_NAME) + n.Attr[transitionPersistIndex].Key = "data-astro-transition-persist" + n.Attr[transitionPersistIndex].Val = transitionNameAttr.Val + n.Attr[transitionPersistIndex].Type = transitionNameAttr.Type + } else { + n.Attr = append(n.Attr, astro.Attribute{ + Key: "data-astro-transition-persist", + Val: fmt.Sprintf(`%s(%s, "%s")`, CREATE_TRANSITION_SCOPE, RESULT, n.TransitionScope), + Type: astro.ExpressionAttribute, + }) + } + } } func (p *printer) printComponentMetadata(doc *astro.Node, opts transform.TransformOptions, source []byte) { diff --git a/internal/printer/printer_test.go b/internal/printer/printer_test.go index 485fa8dea..4ebe7638f 100644 --- a/internal/printer/printer_test.go +++ b/internal/printer/printer_test.go @@ -39,17 +39,23 @@ var RETURN = fmt.Sprintf("return %s%s", TEMPLATE_TAG, BACKTICK) var SUFFIX = fmt.Sprintf("%s;", BACKTICK) + ` }, undefined, undefined); export default $$Component;` +var SUFFIX_EXP_TRANSITIONS = fmt.Sprintf("%s;", BACKTICK) + ` +}, undefined, 'self'); +export default $$Component;` var CREATE_ASTRO_CALL = "const $$Astro = $$createAstro('https://astro.build');\nconst Astro = $$Astro;" var RENDER_HEAD_RESULT = "${$$renderHead($$result)}" // SPECIAL TEST FIXTURES var NON_WHITESPACE_CHARS = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[];:'\",.?") -func suffixWithFilename(filename string) string { - +func suffixWithFilename(filename string, transitions bool) string { + propagationArg := "undefined" + if transitions { + propagationArg = `'self'` + } return fmt.Sprintf("%s;", BACKTICK) + fmt.Sprintf(` -}, '%s', undefined); -export default $$Component;`, filename) +}, '%s', %s); +export default $$Component;`, filename, propagationArg) } type want struct { @@ -69,11 +75,12 @@ type metadata struct { } type testcase struct { - name string - source string - only bool - filename string - want want + name string + source string + only bool + transitions bool + filename string + want want } type jsonTestcase struct { @@ -2792,35 +2799,55 @@ const items = ["Dog", "Cat", "Platipus"]; }, }, { - name: "transition:name with an expression", - source: `
`, - filename: "/projects/app/src/pages/page.astro", + name: "transition:name with an expression", + source: ``, + filename: "/projects/app/src/pages/page.astro", + transitions: true, want: want{ - code: `${$$maybeRenderHead($$result)}`, + code: `${$$maybeRenderHead($$result)}`, }, }, { - name: "transition:name with an template literal", - source: "", - filename: "/projects/app/src/pages/page.astro", + name: "transition:name with an template literal", + source: "", + filename: "/projects/app/src/pages/page.astro", + transitions: true, want: want{ - code: `${$$maybeRenderHead($$result)}`, + code: `${$$maybeRenderHead($$result)}`, }, }, { - name: "transition:animate with an expression", - source: "", - filename: "/projects/app/src/pages/page.astro", + name: "transition:animate with an expression", + source: "", + filename: "/projects/app/src/pages/page.astro", + transitions: true, want: want{ - code: `${$$maybeRenderHead($$result)}`, + code: `${$$maybeRenderHead($$result)}`, }, }, { - name: "transition:animate on Component", - source: `