@planningcenter/tapestry-migration-cli 3.6.0 → 3.7.0-rc.0

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 (34) hide show
  1. package/dist/tapestry-react-shim.cjs +195 -11
  2. package/package.json +3 -3
  3. package/src/components/flex/index.test.ts +181 -0
  4. package/src/components/flex/index.ts +16 -0
  5. package/src/components/flex/transforms/alignmentToAlign.test.ts +101 -0
  6. package/src/components/flex/transforms/alignmentToAlign.ts +74 -20
  7. package/src/components/flex/transforms/auditSpreadProps.test.ts +93 -0
  8. package/src/components/flex/transforms/auditSpreadProps.ts +6 -0
  9. package/src/components/flex/transforms/axisToDirection.test.ts +15 -0
  10. package/src/components/flex/transforms/axisToDirection.ts +26 -0
  11. package/src/components/flex/transforms/constants.ts +4 -0
  12. package/src/components/flex/transforms/convertStyleProps.test.ts +147 -0
  13. package/src/components/flex/transforms/convertStyleProps.ts +23 -0
  14. package/src/components/flex/transforms/cssFlexAliasesToProps.test.ts +202 -0
  15. package/src/components/flex/transforms/cssFlexAliasesToProps.ts +106 -0
  16. package/src/components/flex/transforms/dedupeAttributes.test.ts +120 -0
  17. package/src/components/flex/transforms/dedupeAttributes.ts +49 -0
  18. package/src/components/flex/transforms/distributionToJustify.test.ts +59 -0
  19. package/src/components/flex/transforms/distributionToJustify.ts +69 -20
  20. package/src/components/flex/transforms/flexPropToExpand.test.ts +172 -0
  21. package/src/components/flex/transforms/flexPropToExpand.ts +100 -0
  22. package/src/components/flex/transforms/mediaQueriesToResponsive.test.ts +795 -0
  23. package/src/components/flex/transforms/mediaQueriesToResponsive.ts +562 -0
  24. package/src/components/flex/transforms/paddingPropsToFlex.test.ts +252 -0
  25. package/src/components/flex/transforms/paddingPropsToFlex.ts +150 -0
  26. package/src/components/flex/transforms/setDefaultAxis.test.ts +12 -0
  27. package/src/components/flex/transforms/setDefaultAxis.ts +1 -0
  28. package/src/components/flex/transforms/spacingToGap.test.ts +87 -0
  29. package/src/components/flex/transforms/spacingToGap.ts +28 -10
  30. package/src/components/flex/transforms/unsupportedProps.test.ts +141 -0
  31. package/src/components/flex/transforms/unsupportedProps.ts +17 -0
  32. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +41 -0
  33. package/src/components/shared/transformFactories/stylePropTransformFactory.test.ts +47 -0
  34. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +16 -0
@@ -1,5 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ function _mergeNamespaces(n2, m2) {
4
+ for (var i2 = 0; i2 < m2.length; i2++) {
5
+ const e2 = m2[i2];
6
+ if (typeof e2 !== "string" && !Array.isArray(e2)) {
7
+ for (const k2 in e2) {
8
+ if (k2 !== "default" && !(k2 in n2)) {
9
+ const d2 = Object.getOwnPropertyDescriptor(e2, k2);
10
+ if (d2) {
11
+ Object.defineProperty(n2, k2, d2.get ? d2 : {
12
+ enumerable: true,
13
+ get: () => e2[k2]
14
+ });
15
+ }
16
+ }
17
+ }
18
+ }
19
+ }
20
+ return Object.freeze(Object.defineProperty(n2, Symbol.toStringTag, { value: "Module" }));
21
+ }
3
22
  const bellMuted = "M6.13,2.07l-1.04,-1.03c0.67,-0.48 1.54,-1.04 3.08,-1.04c1.54,0.01 3.01,0.62 4.09,1.68c1.07529,1.04811 1.67813,2.48843 1.67,3.99v4.27l-1.54,-1.55v-2.71c0,-0.54 -0.11,-1.07 -0.32,-1.56c-0.21,-0.5 -0.52,-0.95 -0.92,-1.33c-0.39,-0.38 -0.86,-0.68 -1.37,-0.89c-0.51,-0.21 -1.06,-0.31 -1.62,-0.31c-0.70992,0.00342 -1.40896,0.17475 -2.04,0.5zM16,14.59l-1.4,1.41l-1.74,-1.74h-2.15c-0.2,0.48 -0.54,0.9 -0.99,1.19c-0.45,0.29 -0.97,0.45 -1.51,0.45c-0.54,0 -1.06,-0.16 -1.51,-0.45c-0.45,-0.29 -0.79,-0.71 -0.99,-1.19h-4.08c-0.31,0 -0.62,-0.09 -0.88,-0.25c-0.26165,-0.16091 -0.4676,-0.39827 -0.59,-0.68c-0.13,-0.28 -0.18,-0.59 -0.14,-0.89c0.04,-0.3 0.17,-0.59 0.38,-0.82l2.06,-2.38v-3.58c0,-0.45 0.16,-1.2 0.25,-1.59l-2.11,-2.1l1.4,-1.41zM11.34,12.73l-7.34,-7.36c0,0.1 -0.02,0.21 -0.02,0.29v3.85c0,0.19 -0.07,0.38 -0.19,0.52l-2.23,2.58l0.07,0.12z";
4
23
  const bell = "M15.233,11.506c0.38455,0.4554 0.46962,1.09267 0.218,1.633c-0.23682,0.53936 -0.77094,0.88694 -1.36,0.885h-3.782c-0.377,0.9449 -1.29167,1.56474 -2.309,1.56474c-1.01733,0 -1.932,-0.61985 -2.309,-1.56474h-3.78c-0.59015,0.00313 -1.12575,-0.34463 -1.363,-0.885c-0.25178,-0.54079 -0.16629,-1.1786 0.219,-1.634l1.905,-2.271v-3.417c-0.01886,-2.9628 2.36425,-5.38169 5.327,-5.407c2.9633,0.02476 5.3473,2.44365 5.329,5.407v3.417l1.905,2.272";
5
24
  const bill = "M15.805,2.176c0.12423,0.10317 0.19577,0.25652 0.195,0.418v10.795c0.00021,0.08218 -0.01647,0.16353 -0.049,0.239c-0.11474,0.23112 -0.352,0.37581 -0.61,0.372h-14.665c-0.177,0.003 -0.349,-0.06 -0.482,-0.177c-0.12591,-0.10868 -0.19699,-0.2677 -0.194,-0.434v-10.795c-0.00102,-0.16132 0.07014,-0.31464 0.194,-0.418c0.1326,-0.11756 0.30485,-0.18046 0.482,-0.176h14.664c0.17184,-0.00314 0.33831,0.05986 0.465,0.176M14.798,5.565c-0.11067,0.022 -0.22267,0.03367 -0.336,0.035c-0.64408,-0.00048 -1.26271,-0.25152 -1.725,-0.7c-0.461,-0.447 -0.73,-1.057 -0.75,-1.7h-7.947c-0.0426,1.33678 -1.13754,2.39898 -2.475,2.401c-0.122,-0.00067 -0.243,-0.01333 -0.363,-0.038v4.876c0.11959,-0.0237 0.24109,-0.03642 0.363,-0.038c1.33921,-0.00325 2.43705,1.06133 2.475,2.4h7.948c0.03847,-1.33789 1.13556,-2.40172 2.474,-2.399c0.11276,0.00184 0.22516,0.01321 0.336,0.034zM10.343,5.231c0.31189,0.36828 0.55631,0.78873 0.722,1.242c0.36,0.987 0.36,2.068 0,3.055c-0.166,0.453 -0.41,0.873 -0.721,1.243c-0.2937,0.34506 -0.65341,0.62794 -1.058,0.832c-0.81776,0.40581 -1.77824,0.40581 -2.596,0c-0.406,-0.204 -0.766,-0.487 -1.06,-0.832c-0.31,-0.367 -0.549,-0.789 -0.706,-1.243c-0.34328,-0.9894 -0.34328,-2.0656 0,-3.055c0.15504,-0.45349 0.39234,-0.87453 0.7,-1.242c0.29429,-0.34528 0.6547,-0.62816 1.06,-0.832c0.40423,-0.2014 0.8504,-0.3042 1.302,-0.3c0.45185,-0.00653 0.89857,0.0964 1.302,0.3c0.40351,0.2044 0.76218,0.48725 1.055,0.832M9.334,9.351h-0.422c-0.08583,0.00545 -0.16953,-0.02816 -0.22776,-0.09144c-0.05824,-0.06328 -0.08478,-0.14948 -0.07224,-0.23456v-3.205h-0.883c-0.12661,0.35602 -0.4801,0.57943 -0.856,0.541h-0.17v0.57h0.735v2.093c0.01178,0.08493 -0.01503,0.17068 -0.07309,0.23377c-0.05806,0.06309 -0.14129,0.09692 -0.22691,0.09223h-0.448v0.57h2.642z";
@@ -648,7 +667,7 @@ function requireReact_development() {
648
667
  var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
649
668
  var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
650
669
  var REACT_MEMO_TYPE = Symbol.for("react.memo");
651
- var REACT_LAZY_TYPE = Symbol.for("react.lazy");
670
+ var REACT_LAZY_TYPE2 = Symbol.for("react.lazy");
652
671
  var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
653
672
  var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
654
673
  var FAUX_ITERATOR_SYMBOL = "@@iterator";
@@ -984,7 +1003,7 @@ function requireReact_development() {
984
1003
  return outerName;
985
1004
  }
986
1005
  return getComponentNameFromType(type.type) || "Memo";
987
- case REACT_LAZY_TYPE: {
1006
+ case REACT_LAZY_TYPE2: {
988
1007
  var lazyComponent = type;
989
1008
  var payload = lazyComponent._payload;
990
1009
  var init = lazyComponent._init;
@@ -1534,7 +1553,7 @@ function requireReact_development() {
1534
1553
  _result: ctor
1535
1554
  };
1536
1555
  var lazyType = {
1537
- $$typeof: REACT_LAZY_TYPE,
1556
+ $$typeof: REACT_LAZY_TYPE2,
1538
1557
  _payload: payload,
1539
1558
  _init: lazyInitializer
1540
1559
  };
@@ -1623,7 +1642,7 @@ function requireReact_development() {
1623
1642
  return true;
1624
1643
  }
1625
1644
  if (typeof type === "object" && type !== null) {
1626
- if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
1645
+ if (type.$$typeof === REACT_LAZY_TYPE2 || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
1627
1646
  // types supported by any Flight configuration anywhere since
1628
1647
  // we don't know which Flight build this will end up being used
1629
1648
  // with.
@@ -1978,7 +1997,7 @@ function requireReact_development() {
1978
1997
  return describeFunctionComponentFrame(type.render);
1979
1998
  case REACT_MEMO_TYPE:
1980
1999
  return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
1981
- case REACT_LAZY_TYPE: {
2000
+ case REACT_LAZY_TYPE2: {
1982
2001
  var lazyComponent = type;
1983
2002
  var payload = lazyComponent._payload;
1984
2003
  var init = lazyComponent._init;
@@ -2513,6 +2532,10 @@ function requireReact() {
2513
2532
  }
2514
2533
  var reactExports = requireReact();
2515
2534
  const React = /* @__PURE__ */ getDefaultExportFromCjs(reactExports);
2535
+ const React$1 = /* @__PURE__ */ _mergeNamespaces({
2536
+ __proto__: null,
2537
+ default: React
2538
+ }, [reactExports]);
2516
2539
  function LoadingSpinner() {
2517
2540
  return React.createElement("div", { className: "tds-loading-spinner" });
2518
2541
  }
@@ -2767,14 +2790,175 @@ const PageHeaderActionsDropdownButton = reactExports.forwardRef(({ className, ne
2767
2790
  return React.createElement(DropdownButton, { ref, ...props, kind: "secondary-page-header", ...needsAttentionProps({ className, needsAttention }) });
2768
2791
  });
2769
2792
  PageHeaderActionsDropdownButton.displayName = "PageHeaderActionsDropdownButton";
2770
- const BaseLink = reactExports.forwardRef(({ children, className, external = false, fullWidth, href, kind, label, prefix, size, suffix, ...restProps }, ref) => {
2793
+ function setRef(ref, value) {
2794
+ if (typeof ref === "function") {
2795
+ return ref(value);
2796
+ } else if (ref !== null && ref !== void 0) {
2797
+ ref.current = value;
2798
+ }
2799
+ }
2800
+ function composeRefs(...refs) {
2801
+ return (node) => {
2802
+ let hasCleanup = false;
2803
+ const cleanups = refs.map((ref) => {
2804
+ const cleanup = setRef(ref, node);
2805
+ if (!hasCleanup && typeof cleanup == "function") {
2806
+ hasCleanup = true;
2807
+ }
2808
+ return cleanup;
2809
+ });
2810
+ if (hasCleanup) {
2811
+ return () => {
2812
+ for (let i2 = 0; i2 < cleanups.length; i2++) {
2813
+ const cleanup = cleanups[i2];
2814
+ if (typeof cleanup == "function") {
2815
+ cleanup();
2816
+ } else {
2817
+ setRef(refs[i2], null);
2818
+ }
2819
+ }
2820
+ };
2821
+ }
2822
+ };
2823
+ }
2824
+ function useComposedRefs(...refs) {
2825
+ return reactExports.useCallback(composeRefs(...refs), refs);
2826
+ }
2827
+ // @__NO_SIDE_EFFECTS__
2828
+ function createSlot(ownerName) {
2829
+ const Slot2 = reactExports.forwardRef((props, forwardedRef) => {
2830
+ let { children, ...slotProps } = props;
2831
+ let slottableElement = null;
2832
+ let hasSlottable = false;
2833
+ const newChildren = [];
2834
+ if (isLazyComponent(children) && typeof use === "function") {
2835
+ children = use(children._payload);
2836
+ }
2837
+ reactExports.Children.forEach(children, (maybeSlottable) => {
2838
+ if (isSlottable(maybeSlottable)) {
2839
+ hasSlottable = true;
2840
+ const slottable = maybeSlottable;
2841
+ let child = "child" in slottable.props ? slottable.props.child : slottable.props.children;
2842
+ if (isLazyComponent(child) && typeof use === "function") {
2843
+ child = use(child._payload);
2844
+ }
2845
+ slottableElement = getSlottableElementFromSlottable(slottable, child);
2846
+ newChildren.push(slottableElement?.props?.children);
2847
+ } else {
2848
+ newChildren.push(maybeSlottable);
2849
+ }
2850
+ });
2851
+ if (slottableElement) {
2852
+ slottableElement = reactExports.cloneElement(slottableElement, void 0, newChildren);
2853
+ } else if (
2854
+ // A `Slottable` was found but it didn't resolve to a single element (e.g.
2855
+ // it wrapped multiple elements, text, or a render-prop `child` that
2856
+ // wasn't an element). Don't fall back to treating the `Slottable` wrapper
2857
+ // itself as the slot target — throw a descriptive error below instead.
2858
+ !hasSlottable && reactExports.Children.count(children) === 1 && reactExports.isValidElement(children)
2859
+ ) {
2860
+ slottableElement = children;
2861
+ }
2862
+ const slottableElementRef = slottableElement ? getElementRef(slottableElement) : void 0;
2863
+ const composedRef = useComposedRefs(forwardedRef, slottableElementRef);
2864
+ if (!slottableElement) {
2865
+ if (children || children === 0) {
2866
+ throw new Error(
2867
+ hasSlottable ? createSlottableError(ownerName) : createSlotError(ownerName)
2868
+ );
2869
+ }
2870
+ return children;
2871
+ }
2872
+ const mergedProps = mergeProps(slotProps, slottableElement.props ?? {});
2873
+ if (slottableElement.type !== reactExports.Fragment) {
2874
+ mergedProps.ref = forwardedRef ? composedRef : slottableElementRef;
2875
+ }
2876
+ return reactExports.cloneElement(slottableElement, mergedProps);
2877
+ });
2878
+ Slot2.displayName = `${ownerName}.Slot`;
2879
+ return Slot2;
2880
+ }
2881
+ var Slot = /* @__PURE__ */ createSlot("Slot");
2882
+ var SLOTTABLE_IDENTIFIER = Symbol.for("radix.slottable");
2883
+ // @__NO_SIDE_EFFECTS__
2884
+ function createSlottable(ownerName) {
2885
+ const Slottable2 = (props) => "child" in props ? props.children(props.child) : props.children;
2886
+ Slottable2.displayName = `${ownerName}.Slottable`;
2887
+ Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
2888
+ return Slottable2;
2889
+ }
2890
+ var Slottable = /* @__PURE__ */ createSlottable("Slottable");
2891
+ var getSlottableElementFromSlottable = (slottable, child) => {
2892
+ if ("child" in slottable.props) {
2893
+ const child2 = slottable.props.child;
2894
+ if (!reactExports.isValidElement(child2)) return null;
2895
+ return reactExports.cloneElement(child2, void 0, slottable.props.children(child2.props.children));
2896
+ }
2897
+ return reactExports.isValidElement(child) ? child : null;
2898
+ };
2899
+ function mergeProps(slotProps, childProps) {
2900
+ const overrideProps = { ...childProps };
2901
+ for (const propName in childProps) {
2902
+ const slotPropValue = slotProps[propName];
2903
+ const childPropValue = childProps[propName];
2904
+ const isHandler = /^on[A-Z]/.test(propName);
2905
+ if (isHandler) {
2906
+ if (slotPropValue && childPropValue) {
2907
+ overrideProps[propName] = (...args) => {
2908
+ const result = childPropValue(...args);
2909
+ slotPropValue(...args);
2910
+ return result;
2911
+ };
2912
+ } else if (slotPropValue) {
2913
+ overrideProps[propName] = slotPropValue;
2914
+ }
2915
+ } else if (propName === "style") {
2916
+ overrideProps[propName] = { ...slotPropValue, ...childPropValue };
2917
+ } else if (propName === "className") {
2918
+ overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
2919
+ }
2920
+ }
2921
+ return { ...slotProps, ...overrideProps };
2922
+ }
2923
+ function getElementRef(element) {
2924
+ let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
2925
+ let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
2926
+ if (mayWarn) {
2927
+ return element.ref;
2928
+ }
2929
+ getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
2930
+ mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
2931
+ if (mayWarn) {
2932
+ return element.props.ref;
2933
+ }
2934
+ return element.props.ref || element.ref;
2935
+ }
2936
+ function isSlottable(child) {
2937
+ return reactExports.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
2938
+ }
2939
+ var REACT_LAZY_TYPE = Symbol.for("react.lazy");
2940
+ function isLazyComponent(element) {
2941
+ return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
2942
+ }
2943
+ function isPromiseLike(value) {
2944
+ return typeof value === "object" && value !== null && "then" in value;
2945
+ }
2946
+ var createSlotError = (ownerName) => {
2947
+ return `${ownerName} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`;
2948
+ };
2949
+ var createSlottableError = (ownerName) => {
2950
+ return `${ownerName} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`;
2951
+ };
2952
+ var use = React$1[" use ".trim().toString()];
2953
+ const BaseLink = reactExports.forwardRef(({ asChild = false, children, className, external = false, fullWidth, href, kind, label, prefix, size, suffix, ...restProps }, ref) => {
2771
2954
  const combinedClassName = classNames(kind && "tds-btn", size && size !== "md" && kind && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], fullWidth && "tds-btn--full-width", className, { "tds-btn--prefix": prefix, "tds-btn--suffix": suffix });
2772
2955
  const externalProps = external ? { rel: "noopener noreferrer", target: "_blank" } : {};
2956
+ const Comp = asChild ? Slot : "a";
2773
2957
  return React.createElement(
2774
- "a",
2775
- { href, className: combinedClassName, ref, ...restProps, ...externalProps },
2958
+ Comp,
2959
+ { ...!asChild && { href }, className: combinedClassName, ref, ...restProps, ...externalProps },
2776
2960
  wrapStringWithSpan(prefix),
2777
- label || children,
2961
+ React.createElement(Slottable, null, label || children),
2778
2962
  wrapStringWithSpan(suffix)
2779
2963
  );
2780
2964
  });
@@ -2784,8 +2968,8 @@ const IconLink = reactExports.forwardRef(({ className, href, icon, ...restProps
2784
2968
  return React.createElement(BaseLink, { href, label: icon, className: iconOnlyClassName, ref, ...restProps });
2785
2969
  });
2786
2970
  IconLink.displayName = "IconLink";
2787
- const Link = reactExports.forwardRef(({ href, ...restProps }, ref) => {
2788
- return React.createElement(BaseLink, { href, ref, ...restProps });
2971
+ const Link = reactExports.forwardRef((props, ref) => {
2972
+ return React.createElement(BaseLink, { ref, ...props });
2789
2973
  });
2790
2974
  Link.displayName = "Link";
2791
2975
  const tokens = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/tapestry-migration-cli",
3
- "version": "3.6.0",
3
+ "version": "3.7.0-rc.0",
4
4
  "description": "CLI tool for Tapestry migrations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "devDependencies": {
32
32
  "@emotion/react": "^11.14.0",
33
- "@planningcenter/tapestry": "^3.6.0",
33
+ "@planningcenter/tapestry": "^3.7.0-rc.0",
34
34
  "@planningcenter/tapestry-react": "^4.11.5",
35
35
  "@types/jscodeshift": "^17.3.0",
36
36
  "@types/node": "^20.0.0",
@@ -50,5 +50,5 @@
50
50
  "publishConfig": {
51
51
  "access": "public"
52
52
  },
53
- "gitHead": "0009d96d1c39de5978d2cdea8334ca5120974137"
53
+ "gitHead": "4f3ec50db17c53189754207e74dca9c71f57d0f5"
54
54
  }
@@ -99,6 +99,187 @@ export default function Test() {
99
99
  expect(result).not.toContain("innerRef")
100
100
  })
101
101
 
102
+ it("applies paddingPropsToFlex through the chain", () => {
103
+ const input = `
104
+ import { StackView } from "@planningcenter/tapestry-react"
105
+
106
+ export default function Test() {
107
+ return <StackView axis="horizontal" padding="8px" paddingHorizontal="16px">Test</StackView>
108
+ }
109
+ `.trim()
110
+
111
+ const result = applyTransform(input)
112
+ expect(result).toContain("padding={1}")
113
+ expect(result).toContain("paddingInline={2}")
114
+ expect(result).not.toContain('padding="8px"')
115
+ expect(result).not.toContain("paddingHorizontal")
116
+ })
117
+
118
+ it("applies flexPropToExpand through the chain — flex={1} becomes expand", () => {
119
+ const input = `
120
+ import { StackView } from "@planningcenter/tapestry-react"
121
+
122
+ export default function Test() {
123
+ return <StackView flex={1}>Test</StackView>
124
+ }
125
+ `.trim()
126
+
127
+ const result = applyTransform(input)
128
+ expect(result).toContain("expand")
129
+ expect(result).not.toContain("flex")
130
+ })
131
+
132
+ it('applies flexPropToExpand through the chain — flex="1 0 0" becomes grow/shrink/basis', () => {
133
+ const input = `
134
+ import { StackView } from "@planningcenter/tapestry-react"
135
+
136
+ export default function Test() {
137
+ return <StackView flex="1 0 0">Test</StackView>
138
+ }
139
+ `.trim()
140
+
141
+ const result = applyTransform(input)
142
+ expect(result).toContain("grow={1}")
143
+ expect(result).toContain("shrink={0}")
144
+ expect(result).toContain('basis="0"')
145
+ expect(result).not.toContain("flex=")
146
+ })
147
+
148
+ it("applies flexPropToExpand through the chain — unmappable flex stays as JSX prop with TODO, not in style={{}}", () => {
149
+ const input = `
150
+ import { StackView } from "@planningcenter/tapestry-react"
151
+
152
+ export default function Test({ dynamicValue }) {
153
+ return <StackView flex={dynamicValue}>Test</StackView>
154
+ }
155
+ `.trim()
156
+
157
+ const result = applyTransform(input)
158
+ expect(result).toContain("flex={dynamicValue}")
159
+ expect(result).not.toContain("style={{")
160
+ expect(result?.match(/TODO/g)).toHaveLength(1)
161
+ })
162
+
163
+ it("applies mediaQueriesToResponsive through the chain", () => {
164
+ const input = `
165
+ import { StackView } from "@planningcenter/tapestry-react"
166
+
167
+ export default function Test() {
168
+ return <StackView axis="horizontal" mediaQueries={{ md: { axis: "vertical" } }}>Test</StackView>
169
+ }
170
+ `.trim()
171
+
172
+ const result = applyTransform(input)
173
+ expect(result).toContain("responsive=")
174
+ expect(result).toContain('direction: "column"')
175
+ expect(result).not.toContain("mediaQueries=")
176
+ })
177
+
178
+ it("applies cssFlexAliasesToProps through the chain — flexDirection does not produce a double direction prop", () => {
179
+ const input = `
180
+ import { StackView } from "@planningcenter/tapestry-react"
181
+
182
+ export default function Test() {
183
+ return <StackView flexDirection="row">Test</StackView>
184
+ }
185
+ `.trim()
186
+
187
+ const result = applyTransform(input)
188
+ expect(result).toContain('direction="row"')
189
+ expect(result?.match(/direction=/g)).toHaveLength(1)
190
+ })
191
+
192
+ it("applies dedupeAttributes through the chain — axis and flexDirection both mapping to direction collapse to one prop", () => {
193
+ const input = `
194
+ import { StackView } from "@planningcenter/tapestry-react"
195
+
196
+ export default function Test() {
197
+ return <StackView axis="horizontal" flexDirection="column">Test</StackView>
198
+ }
199
+ `.trim()
200
+
201
+ const result = applyTransform(input)
202
+ expect(result?.match(/direction=/g)).toHaveLength(1)
203
+ expect(result).toContain('direction="column"')
204
+ expect(result).toContain("TODO: tapestry-migration (direction)")
205
+ })
206
+
207
+ it("applies cssFlexAliasesToProps through the chain — unmappable justifyContent stays as JSX prop with TODO, not in style={{}}", () => {
208
+ const input = `
209
+ import { StackView } from "@planningcenter/tapestry-react"
210
+
211
+ export default function Test() {
212
+ return <StackView justifyContent="stretch">Test</StackView>
213
+ }
214
+ `.trim()
215
+
216
+ const result = applyTransform(input)
217
+ expect(result).toContain("TODO")
218
+ expect(result).toContain("justifyContent")
219
+ expect(result).not.toContain("style={{")
220
+ })
221
+
222
+ it("applies cssFlexAliasesToProps through the chain — flexGrow/flexShrink/flexBasis become Flex props", () => {
223
+ const input = `
224
+ import { StackView } from "@planningcenter/tapestry-react"
225
+
226
+ export default function Test() {
227
+ return <StackView flexGrow={1} flexShrink={0} flexBasis="auto">Test</StackView>
228
+ }
229
+ `.trim()
230
+
231
+ const result = applyTransform(input)
232
+ expect(result).toContain("grow={1}")
233
+ expect(result).toContain("shrink={0}")
234
+ expect(result).toContain('basis="auto"')
235
+ expect(result).not.toContain("flexGrow")
236
+ expect(result).not.toContain("flexShrink")
237
+ expect(result).not.toContain("flexBasis")
238
+ expect(result).not.toContain("style={{")
239
+ })
240
+
241
+ it("applies convertStyleProps through the chain", () => {
242
+ const input = `
243
+ import { StackView } from "@planningcenter/tapestry-react"
244
+
245
+ export default function Test() {
246
+ return <StackView axis="horizontal" marginTop={2}>Test</StackView>
247
+ }
248
+ `.trim()
249
+
250
+ const result = applyTransform(input)
251
+ expect(result).toContain("style={{")
252
+ expect(result).toContain("marginTop:")
253
+ expect(result).not.toContain("marginTop={2}")
254
+ })
255
+
256
+ it("applies unsupportedProps through the chain", () => {
257
+ const input = `
258
+ import { StackView } from "@planningcenter/tapestry-react"
259
+
260
+ export default function Test() {
261
+ return <StackView axis="horizontal" visible={isOpen}>Test</StackView>
262
+ }
263
+ `.trim()
264
+
265
+ const result = applyTransform(input)
266
+ expect(result).toContain("TODO: tapestry-migration (visible)")
267
+ })
268
+
269
+ it("applies auditSpreadProps through the chain", () => {
270
+ const input = `
271
+ import { StackView } from "@planningcenter/tapestry-react"
272
+
273
+ export default function Test() {
274
+ return <StackView axis="horizontal" {...props}>Test</StackView>
275
+ }
276
+ `.trim()
277
+
278
+ const result = applyTransform(input)
279
+ expect(result).toContain("TODO: tapestry-migration (spreadAttribute)")
280
+ expect(result).toContain("{...props}")
281
+ })
282
+
102
283
  it("applies moveFlexImport through the chain", () => {
103
284
  const input = `
104
285
  import { StackView } from "@planningcenter/tapestry-react"
@@ -1,24 +1,40 @@
1
1
  import { Transform } from "jscodeshift"
2
2
 
3
3
  import alignmentToAlign from "./transforms/alignmentToAlign"
4
+ import auditSpreadProps from "./transforms/auditSpreadProps"
4
5
  import axisToDirection from "./transforms/axisToDirection"
6
+ import convertStyleProps from "./transforms/convertStyleProps"
7
+ import cssFlexAliasesToProps from "./transforms/cssFlexAliasesToProps"
8
+ import dedupeAttributes from "./transforms/dedupeAttributes"
5
9
  import distributionToJustify from "./transforms/distributionToJustify"
10
+ import flexPropToExpand from "./transforms/flexPropToExpand"
6
11
  import innerRefToRef from "./transforms/innerRefToRef"
12
+ import mediaQueriesToResponsive from "./transforms/mediaQueriesToResponsive"
7
13
  import moveFlexImport from "./transforms/moveFlexImport"
14
+ import paddingPropsToFlex from "./transforms/paddingPropsToFlex"
8
15
  import setDefaultAxis from "./transforms/setDefaultAxis"
9
16
  import spacingToGap from "./transforms/spacingToGap"
17
+ import unsupportedProps from "./transforms/unsupportedProps"
10
18
 
11
19
  const transform: Transform = (fileInfo, api, options) => {
12
20
  let currentSource = fileInfo.source
13
21
  let hasAnyChanges = false
14
22
 
15
23
  const transforms = [
24
+ cssFlexAliasesToProps,
16
25
  setDefaultAxis,
26
+ paddingPropsToFlex,
27
+ flexPropToExpand,
17
28
  axisToDirection,
18
29
  alignmentToAlign,
19
30
  distributionToJustify,
20
31
  spacingToGap,
21
32
  innerRefToRef,
33
+ mediaQueriesToResponsive,
34
+ convertStyleProps,
35
+ unsupportedProps,
36
+ auditSpreadProps,
37
+ dedupeAttributes,
22
38
  moveFlexImport,
23
39
  ]
24
40
 
@@ -100,6 +100,107 @@ export default function Test() {
100
100
  expect(result).not.toContain("StackView.BASELINE")
101
101
  })
102
102
 
103
+ it('normalizes alignment="flex-start" to align="start"', () => {
104
+ const input = `
105
+ import { StackView } from "@planningcenter/tapestry-react"
106
+
107
+ export default function Test() {
108
+ return <StackView alignment="flex-start">Test</StackView>
109
+ }
110
+ `.trim()
111
+
112
+ const result = applyTransform(input)
113
+ expect(result).toContain('align="start"')
114
+ expect(result).not.toContain("alignment")
115
+ })
116
+
117
+ it('normalizes alignment="flex-end" to align="end"', () => {
118
+ const input = `
119
+ import { StackView } from "@planningcenter/tapestry-react"
120
+
121
+ export default function Test() {
122
+ return <StackView alignment="flex-end">Test</StackView>
123
+ }
124
+ `.trim()
125
+
126
+ const result = applyTransform(input)
127
+ expect(result).toContain('align="end"')
128
+ expect(result).not.toContain("alignment")
129
+ })
130
+
131
+ it("migrates a ternary with a valid string branch and an undefined pass-through branch", () => {
132
+ const input = `
133
+ import { StackView } from "@planningcenter/tapestry-react"
134
+
135
+ export default function Test({ compact }) {
136
+ return <StackView alignment={compact ? 'center' : undefined}>Test</StackView>
137
+ }
138
+ `.trim()
139
+
140
+ const result = applyTransform(input)
141
+ expect(result).toContain('align={compact ? "center" : undefined}')
142
+ expect(result).not.toContain("alignment")
143
+ })
144
+
145
+ it("migrates a ternary with two valid string branches", () => {
146
+ const input = `
147
+ import { StackView } from "@planningcenter/tapestry-react"
148
+
149
+ export default function Test({ compact }) {
150
+ return <StackView alignment={compact ? 'center' : 'end'}>Test</StackView>
151
+ }
152
+ `.trim()
153
+
154
+ const result = applyTransform(input)
155
+ expect(result).toContain('align={compact ? "center" : "end"}')
156
+ expect(result).not.toContain("alignment")
157
+ })
158
+
159
+ it("normalizes flex-start/flex-end within a ternary", () => {
160
+ const input = `
161
+ import { StackView } from "@planningcenter/tapestry-react"
162
+
163
+ export default function Test({ compact }) {
164
+ return <StackView alignment={compact ? 'flex-start' : 'flex-end'}>Test</StackView>
165
+ }
166
+ `.trim()
167
+
168
+ const result = applyTransform(input)
169
+ expect(result).toContain('align={compact ? "start" : "end"}')
170
+ expect(result).not.toContain("alignment")
171
+ })
172
+
173
+ it("migrates a ternary with a dynamic branch, trusting it at runtime", () => {
174
+ const input = `
175
+ import { StackView } from "@planningcenter/tapestry-react"
176
+
177
+ export default function Test({ compact, dynamicValue }) {
178
+ return <StackView alignment={compact ? 'center' : dynamicValue}>Test</StackView>
179
+ }
180
+ `.trim()
181
+
182
+ const result = applyTransform(input)
183
+ expect(result).toContain('align={compact ? "center" : dynamicValue}')
184
+ expect(result).not.toContain("alignment")
185
+ expect(result).not.toContain("TODO")
186
+ })
187
+
188
+ it("flags a ternary with a fill branch and leaves it in place", () => {
189
+ const input = `
190
+ import { StackView } from "@planningcenter/tapestry-react"
191
+
192
+ export default function Test({ compact }) {
193
+ return <StackView alignment={compact ? 'center' : 'fill'}>Test</StackView>
194
+ }
195
+ `.trim()
196
+
197
+ const result = applyTransform(input)
198
+ expect(result).toContain("alignment={compact ? 'center' : 'fill'}")
199
+ expect(result).toContain("TODO: tapestry-migration (alignment)")
200
+ expect(result).not.toContain('align="')
201
+ expect(result).not.toContain("align={")
202
+ })
203
+
103
204
  it("flags alignment={StackView.FILL} with a TODO and leaves it in place", () => {
104
205
  const input = `
105
206
  import { StackView } from "@planningcenter/tapestry-react"