@jay-framework/compiler-jay-html 0.19.5 → 0.19.7

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.
package/dist/index.d.cts CHANGED
@@ -180,6 +180,12 @@ interface JayHtmlHeadLink {
180
180
  href: string;
181
181
  attributes: Record<string, string>;
182
182
  }
183
+ interface JayHtmlScript {
184
+ src?: string;
185
+ inline?: string;
186
+ attributes: Record<string, string>;
187
+ position: 'head' | 'body';
188
+ }
183
189
  interface JayHtmlSourceFile extends CompilerSourceFile {
184
190
  format: SourceFileFormat.JayHtml;
185
191
  baseElementName: string;
@@ -214,6 +220,7 @@ interface JayHtmlSourceFile extends CompilerSourceFile {
214
220
  */
215
221
  clientTrackByMap?: Record<string, string>;
216
222
  headMeta?: JayHtmlHeadMeta;
223
+ scripts?: JayHtmlScript[];
217
224
  }
218
225
 
219
226
  declare function parseTemplateParts(value: string): TemplatePart[];
@@ -601,4 +608,4 @@ declare function resolveHeadlessInstances(preRenderedJayHtml: string, instanceDa
601
608
  */
602
609
  declare function hasSlowPhaseProperties(contract: Contract | undefined): boolean;
603
610
 
604
- export { type ActionDefinition, type AssignCoordinatesOptions, type AssignCoordinatesResult, type Contract, type ContractImportInfo, type ContractParam, type ContractParamKind, type ContractProp, type ContractResolver, type ContractTag, ContractTagType, type DiscoveredHeadlessInstance, type EnumToImport, type ForEachHeadlessInstance, type HeadlessContractInfo, type HeadlessInstanceDiscoveryResult, type HeadlessInstanceResolvedData, JAY_IMPORT_RESOLVER, type JayContractImportLink, type JayHtmlSourceFile, type JayImportResolver, type PhaseViewStates, type RenderingPhase, type ServerElementOptions, type SlowRenderInput, type SlowRenderOutput, assignCoordinates, assignCoordinatesToJayHtml, buildInstanceCoordinateKey, compileAction, compileContract, contractToAllPhaseViewStates, contractToImportsViewStateAndRefs, contractToPhaseViewState, createPhaseContract, defaultContractResolver, discoverHeadlessInstances, filterTagsByPhase, generateElementBridgeFile, generateElementDefinitionFile, generateElementFile, generateElementFileReactTarget, generateElementHydrateFile, generateSandboxRootFile, generateServerElementFile, generateTypes, getEffectivePhase, getJayHtmlImports, getLinkedContractDir, hasSlowPhaseProperties, htmlElementTagNameMap, injectHeadfullFSTemplates, isTagInPhase, loadLinkedContract, parseAction, parseContract, parseEnumValues, parseIsEnum, parseJayFile, parseTemplateParts, renderRefsType, resolveHeadlessInstances, slowRenderTransform, validateContractPhases };
611
+ export { type ActionDefinition, type AssignCoordinatesOptions, type AssignCoordinatesResult, type Contract, type ContractImportInfo, type ContractParam, type ContractParamKind, type ContractProp, type ContractResolver, type ContractTag, ContractTagType, type DiscoveredHeadlessInstance, type EnumToImport, type ForEachHeadlessInstance, type HeadlessContractInfo, type HeadlessInstanceDiscoveryResult, type HeadlessInstanceResolvedData, JAY_IMPORT_RESOLVER, type JayContractImportLink, type JayHtmlScript, type JayHtmlSourceFile, type JayImportResolver, type PhaseViewStates, type RenderingPhase, type ServerElementOptions, type SlowRenderInput, type SlowRenderOutput, assignCoordinates, assignCoordinatesToJayHtml, buildInstanceCoordinateKey, compileAction, compileContract, contractToAllPhaseViewStates, contractToImportsViewStateAndRefs, contractToPhaseViewState, createPhaseContract, defaultContractResolver, discoverHeadlessInstances, filterTagsByPhase, generateElementBridgeFile, generateElementDefinitionFile, generateElementFile, generateElementFileReactTarget, generateElementHydrateFile, generateSandboxRootFile, generateServerElementFile, generateTypes, getEffectivePhase, getJayHtmlImports, getLinkedContractDir, hasSlowPhaseProperties, htmlElementTagNameMap, injectHeadfullFSTemplates, isTagInPhase, loadLinkedContract, parseAction, parseContract, parseEnumValues, parseIsEnum, parseJayFile, parseTemplateParts, renderRefsType, resolveHeadlessInstances, slowRenderTransform, validateContractPhases };
package/dist/index.js CHANGED
@@ -14657,16 +14657,52 @@ function mergeContractStubRefs(templateRefs, contractRefs) {
14657
14657
  );
14658
14658
  }
14659
14659
  }
14660
- if (stubRefs.length === 0)
14660
+ const mergedChildren = { ...templateRefs.children };
14661
+ for (const [childName, contractChild] of Object.entries(contractRefs.children)) {
14662
+ if (mergedChildren[childName]) {
14663
+ mergedChildren[childName] = mergeContractStubRefs(
14664
+ mergedChildren[childName],
14665
+ contractChild
14666
+ );
14667
+ } else {
14668
+ mergedChildren[childName] = markAllRefsAsStubs(contractChild);
14669
+ }
14670
+ }
14671
+ const childrenChanged = Object.keys(mergedChildren).length !== Object.keys(templateRefs.children).length || Object.keys(mergedChildren).some((k) => mergedChildren[k] !== templateRefs.children[k]);
14672
+ if (stubRefs.length === 0 && !childrenChanged)
14661
14673
  return templateRefs;
14662
14674
  return mkRefsTree(
14663
14675
  [...templateRefs.refs, ...stubRefs],
14664
- templateRefs.children,
14676
+ mergedChildren,
14665
14677
  templateRefs.repeated,
14666
14678
  templateRefs.imported?.refsTypeName,
14667
14679
  templateRefs.imported?.repeatedRefsTypeName
14668
14680
  );
14669
14681
  }
14682
+ function markAllRefsAsStubs(tree) {
14683
+ const stubbedRefs = tree.refs.map(
14684
+ (ref) => mkRef(
14685
+ ref.originalName,
14686
+ ref.originalName,
14687
+ camelCase(`ref ${ref.originalName}`),
14688
+ ref.repeated,
14689
+ true,
14690
+ ref.viewStateType,
14691
+ ref.elementType
14692
+ )
14693
+ );
14694
+ const stubbedChildren = {};
14695
+ for (const [name, child] of Object.entries(tree.children)) {
14696
+ stubbedChildren[name] = markAllRefsAsStubs(child);
14697
+ }
14698
+ return mkRefsTree(
14699
+ stubbedRefs,
14700
+ stubbedChildren,
14701
+ tree.repeated,
14702
+ tree.imported?.refsTypeName,
14703
+ tree.imported?.repeatedRefsTypeName
14704
+ );
14705
+ }
14670
14706
  const COORD_ATTR = "jay-coordinate-base";
14671
14707
  function expandContractType(renderedElement, baseName) {
14672
14708
  const contractPattern = new RegExp(
@@ -18264,7 +18300,11 @@ function parseHeadfullImports(elements, validations, filePath, options, importRe
18264
18300
  const exportedTypes = importResolver.analyzeExportedTypes(importedFile, options);
18265
18301
  for (const name of names) {
18266
18302
  const exportedType = exportedTypes.find((_) => _.name === name.name);
18267
- if (exportedType && exportedType !== JayUnknown)
18303
+ if (exportedType instanceof JayComponentType && exportedType.fullStack) {
18304
+ validations.push(
18305
+ `${name.name} from ${module} is a full-stack component (makeJayStackComponent). Create a .jay-contract file for the component and add a contract attribute to the import`
18306
+ );
18307
+ } else if (exportedType && exportedType !== JayUnknown)
18268
18308
  name.type = new JayImportedType(name.as ? name.as : name.name, exportedType);
18269
18309
  else if (exportedType === JayUnknown)
18270
18310
  validations.push(
@@ -18746,6 +18786,49 @@ async function parseHeadfullFSImports(elements, validations, filePath, importRes
18746
18786
  function normalizeFilename(filename) {
18747
18787
  return filename.replace(".jay-html", "");
18748
18788
  }
18789
+ function isLocalSrc(src) {
18790
+ return src.startsWith("./") || src.startsWith("../") || !src.includes("://") && !src.startsWith("//");
18791
+ }
18792
+ function validateAndCollectScripts(root, validations) {
18793
+ const scripts = [];
18794
+ const allScripts = root.querySelectorAll("script");
18795
+ for (const script of allScripts) {
18796
+ const type2 = script.getAttribute("type");
18797
+ if (type2?.startsWith("application/jay-"))
18798
+ continue;
18799
+ const jayScript = script.getAttribute("jay-script");
18800
+ const src = script.getAttribute("src");
18801
+ const inline = script.textContent?.trim();
18802
+ const isInHead = !!script.closest("head");
18803
+ if (src && isLocalSrc(src)) {
18804
+ validations.push(
18805
+ `Local script imports are not supported in jay-html. Move the script logic into page.ts with makeJayStackComponent. See designer/script-tags.md.`
18806
+ );
18807
+ continue;
18808
+ }
18809
+ if (jayScript === "allow") {
18810
+ const attributes2 = { ...script.attributes };
18811
+ delete attributes2["jay-script"];
18812
+ delete attributes2["src"];
18813
+ if (src) {
18814
+ scripts.push({ src, attributes: attributes2, position: isInHead ? "head" : "body" });
18815
+ } else if (inline) {
18816
+ scripts.push({ inline, attributes: attributes2, position: isInHead ? "head" : "body" });
18817
+ }
18818
+ continue;
18819
+ }
18820
+ if (src) {
18821
+ validations.push(
18822
+ `External scripts should be explicitly marked. If this script is required (e.g., analytics or tag manager), add jay-script="allow". Prefer page.ts for page behavior. See designer/script-tags.md.`
18823
+ );
18824
+ } else if (inline) {
18825
+ validations.push(
18826
+ `Inline scripts are not supported in jay-html. Use page.ts with makeJayStackComponent for page behavior. If this is a third-party script that must be included as-is, add jay-script="allow". See designer/script-tags.md.`
18827
+ );
18828
+ }
18829
+ }
18830
+ return scripts;
18831
+ }
18749
18832
  function parseHeadLinks(root, excludeCssLinks = false) {
18750
18833
  const allLinks = root.querySelectorAll("head link");
18751
18834
  return allLinks.filter((link) => {
@@ -18766,12 +18849,20 @@ function parseHeadLinks(root, excludeCssLinks = false) {
18766
18849
  };
18767
18850
  });
18768
18851
  }
18852
+ function toParts(value) {
18853
+ try {
18854
+ return parseTemplateParts(value);
18855
+ } catch {
18856
+ return [{ kind: "static", value }];
18857
+ }
18858
+ }
18769
18859
  function parseHeadMeta(root) {
18770
18860
  const head = root.querySelector("head");
18771
18861
  if (!head)
18772
18862
  return void 0;
18773
18863
  const titleEl = head.querySelector("title");
18774
- const title = titleEl?.textContent?.trim() || void 0;
18864
+ const titleText = titleEl?.textContent?.trim();
18865
+ const title = titleText ? toParts(titleText) : void 0;
18775
18866
  const meta = [];
18776
18867
  for (const el of head.querySelectorAll("meta")) {
18777
18868
  const content = el.getAttribute("content");
@@ -18780,7 +18871,11 @@ function parseHeadMeta(root) {
18780
18871
  const name = el.getAttribute("name");
18781
18872
  const property = el.getAttribute("property");
18782
18873
  if (name || property) {
18783
- meta.push({ name: name || void 0, property: property || void 0, content });
18874
+ meta.push({
18875
+ name: name || void 0,
18876
+ property: property || void 0,
18877
+ content: toParts(content)
18878
+ });
18784
18879
  }
18785
18880
  }
18786
18881
  const links = [];
@@ -18788,8 +18883,13 @@ function parseHeadMeta(root) {
18788
18883
  const rel = el.getAttribute("rel");
18789
18884
  if (!rel)
18790
18885
  continue;
18791
- const attrs = { ...el.attributes };
18792
- links.push(attrs);
18886
+ const hrefRaw = el.getAttribute("href") || "";
18887
+ const extraAttrs = {};
18888
+ for (const [k, v] of Object.entries(el.attributes)) {
18889
+ if (k !== "rel" && k !== "href")
18890
+ extraAttrs[k] = v;
18891
+ }
18892
+ links.push({ rel, href: toParts(hrefRaw), ...extraAttrs });
18793
18893
  }
18794
18894
  return { title, meta, links };
18795
18895
  }
@@ -18928,6 +19028,7 @@ async function parseJayFile(html2, filename, filePath, options, linkedContractRe
18928
19028
  const excludeCssLinks = !!filePath;
18929
19029
  const headLinks = parseHeadLinks(root, excludeCssLinks);
18930
19030
  const headMeta = parseHeadMeta(root);
19031
+ const scripts = validateAndCollectScripts(root, validations);
18931
19032
  validations.push(...cssResult.validations);
18932
19033
  if (validations.length > 0)
18933
19034
  return new WithValidations(void 0, validations);
@@ -18963,7 +19064,8 @@ async function parseJayFile(html2, filename, filePath, options, linkedContractRe
18963
19064
  hasInlineData: jayYaml.hasInlineData,
18964
19065
  serverTrackByMap: Object.keys(serverTrackByMap).length > 0 ? serverTrackByMap : void 0,
18965
19066
  clientTrackByMap: Object.keys(clientTrackByMap).length > 0 ? clientTrackByMap : void 0,
18966
- headMeta
19067
+ headMeta,
19068
+ scripts: scripts.length > 0 ? scripts : void 0
18967
19069
  },
18968
19070
  validations
18969
19071
  );
@@ -19855,6 +19957,11 @@ ${context.indent.firstLine} ${createBody})`,
19855
19957
  childContent.refs
19856
19958
  );
19857
19959
  }
19960
+ if (isConditional(element) && isForEach(element)) {
19961
+ return new RenderFragment("", Imports.none(), [
19962
+ `"if" and "forEach" cannot be on the same element. Wrap the forEach in a separate element: <div if="..."><div forEach="...">...</div></div>`
19963
+ ]);
19964
+ }
19858
19965
  if (isConditional(element) && context.interactivePaths.size > 0) {
19859
19966
  const condition = element.getAttribute("if");
19860
19967
  const viewStateVars = new Variables(
@@ -21279,6 +21386,10 @@ const ${componentSymbol} = makeHeadlessInstanceComponent(
21279
21386
  mkRefsTree([], {})
21280
21387
  );
21281
21388
  }
21389
+ } else if (isConditional(htmlElement) && isForEach(htmlElement)) {
21390
+ return new RenderFragment("", Imports.none(), [
21391
+ `"if" and "forEach" cannot be on the same element. Wrap the forEach in a separate element: <div if="..."><div forEach="...">...</div></div>`
21392
+ ]);
21282
21393
  } else if (isConditional(htmlElement)) {
21283
21394
  let condition = htmlElement.getAttribute("if");
21284
21395
  let childElement = renderHtmlElement(htmlElement, {
@@ -22384,6 +22495,11 @@ function renderServerElement(element, context) {
22384
22495
  if (componentMatch !== null && componentMatch.kind === "headless-instance") {
22385
22496
  return renderServerHeadlessInstance(element, context, componentMatch.name);
22386
22497
  }
22498
+ if (isConditional(element) && isForEach(element)) {
22499
+ return new RenderFragment("", Imports.none(), [
22500
+ `"if" and "forEach" cannot be on the same element. Wrap the forEach in a separate element: <div if="..."><div forEach="...">...</div></div>`
22501
+ ]);
22502
+ }
22387
22503
  if (isConditional(element)) {
22388
22504
  const condition = element.getAttribute("if");
22389
22505
  const renderedCondition = parseServerCondition(condition, variables);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/compiler-jay-html",
3
- "version": "0.19.5",
3
+ "version": "0.19.7",
4
4
  "description": "",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -34,12 +34,12 @@
34
34
  },
35
35
  "author": "",
36
36
  "dependencies": {
37
- "@jay-framework/compiler-analyze-exported-types": "^0.19.5",
38
- "@jay-framework/compiler-shared": "^0.19.5",
39
- "@jay-framework/component": "^0.19.5",
40
- "@jay-framework/logger": "^0.19.5",
41
- "@jay-framework/runtime": "^0.19.5",
42
- "@jay-framework/secure": "^0.19.5",
37
+ "@jay-framework/compiler-analyze-exported-types": "^0.19.7",
38
+ "@jay-framework/compiler-shared": "^0.19.7",
39
+ "@jay-framework/component": "^0.19.7",
40
+ "@jay-framework/logger": "^0.19.7",
41
+ "@jay-framework/runtime": "^0.19.7",
42
+ "@jay-framework/secure": "^0.19.7",
43
43
  "@types/js-yaml": "^4.0.9",
44
44
  "change-case": "^4.1.2",
45
45
  "he": "^1.2.0",
@@ -52,8 +52,8 @@
52
52
  },
53
53
  "devDependencies": {
54
54
  "@caiogondim/strip-margin": "^1.0.0",
55
- "@jay-framework/4-react": "^0.19.5",
56
- "@jay-framework/dev-environment": "^0.19.5",
55
+ "@jay-framework/4-react": "^0.19.7",
56
+ "@jay-framework/dev-environment": "^0.19.7",
57
57
  "@testing-library/jest-dom": "^6.2.0",
58
58
  "@types/js-beautify": "^1",
59
59
  "@types/node": "^20.11.5",