@jay-framework/compiler-jay-html 0.14.0 → 0.15.1

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
@@ -31,9 +31,11 @@ interface ContractProp {
31
31
  description?: Array<string>;
32
32
  default?: string;
33
33
  }
34
- /** URL/load params for a page (e.g. [slug]). Always string in generated type (UrlParams = Record<string, string>). Design Log #85. */
34
+ type ContractParamKind = 'required' | 'optional' | 'catch-all';
35
+ /** URL/load params for a page (e.g. [slug]). Design Log #85, #113. */
35
36
  interface ContractParam {
36
37
  name: string;
38
+ kind: ContractParamKind;
37
39
  }
38
40
  interface Contract {
39
41
  name: string;
@@ -90,6 +92,13 @@ interface JayImportResolver {
90
92
  metadata?: Record<string, unknown>;
91
93
  }>;
92
94
  resolvePluginManifest(pluginName: string, projectRoot: string): WithValidations<PluginManifest>;
95
+ /**
96
+ * Read a jay-html file adjacent to a component module.
97
+ * Given a module src path (e.g., "./header/header"), resolves to the absolute path
98
+ * and reads the corresponding .jay-html file (same base name + .jay-html extension).
99
+ * Returns the file content string, or null if not found.
100
+ */
101
+ readJayHtml(importingModuleDir: string, src: string): string | null;
93
102
  }
94
103
  declare const JAY_IMPORT_RESOLVER: JayImportResolver;
95
104
 
@@ -243,6 +252,21 @@ declare function assignCoordinatesToJayHtml(jayHtml: string, headlessContractNam
243
252
  */
244
253
  declare function assignCoordinates(body: HTMLElement, options: AssignCoordinatesOptions): AssignCoordinatesResult;
245
254
 
255
+ /**
256
+ * Inject headfull full-stack component templates into jay-html.
257
+ * Finds <script type="application/jay-headfull" contract="..."> tags,
258
+ * reads each component's jay-html file, and injects the body content
259
+ * into matching <jay:Name> tags that are empty/self-closing.
260
+ *
261
+ * This must be called on the raw HTML string BEFORE the slow render pipeline,
262
+ * so that instance bindings can be resolved during pre-rendering.
263
+ *
264
+ * @param html - The raw jay-html content
265
+ * @param sourceDir - Absolute path to the directory containing the jay-html file
266
+ * @param importResolver - Resolver for reading component jay-html files
267
+ * @returns The HTML with headfull FS templates injected (or unchanged if none found)
268
+ */
269
+ declare function injectHeadfullFSTemplates(html: string, sourceDir: string, importResolver: JayImportResolver): string;
246
270
  declare function parseJayFile(html: string, filename: string, filePath: string, options: ResolveTsConfigOptions, linkedContractResolver: JayImportResolver, projectRoot: string): Promise<WithValidations<JayHtmlSourceFile>>;
247
271
  declare function getJayHtmlImports(html: string): string[];
248
272
 
@@ -429,4 +453,4 @@ declare function resolveHeadlessInstances(preRenderedJayHtml: string, instanceDa
429
453
  */
430
454
  declare function hasSlowPhaseProperties(contract: Contract | undefined): boolean;
431
455
 
432
- export { type ActionDefinition, type AssignCoordinatesOptions, type AssignCoordinatesResult, type Contract, type ContractImportInfo, type ContractParam, 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, isTagInPhase, loadLinkedContract, parseAction, parseContract, parseEnumValues, parseIsEnum, parseJayFile, renderRefsType, resolveHeadlessInstances, slowRenderTransform, validateContractPhases };
456
+ 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, injectHeadfullFSTemplates, isTagInPhase, loadLinkedContract, parseAction, parseContract, parseEnumValues, parseIsEnum, parseJayFile, renderRefsType, resolveHeadlessInstances, slowRenderTransform, validateContractPhases };
package/dist/index.js CHANGED
@@ -5845,7 +5845,7 @@ function assignCoordinates(body, options) {
5845
5845
  walkChildren(rootElement, "0", options, newScope());
5846
5846
  return { debugHtml: body.toString() };
5847
5847
  }
5848
- function walkChildren(parent, parentCoord, options, scope) {
5848
+ function walkChildren(parent, parentCoord, options, scope, slowForEachPrefix = "") {
5849
5849
  for (const child of parent.childNodes) {
5850
5850
  if (child.nodeType !== NodeType.ELEMENT_NODE)
5851
5851
  continue;
@@ -5861,7 +5861,14 @@ function walkChildren(parent, parentCoord, options, scope) {
5861
5861
  ref = `AR${idx}`;
5862
5862
  element.setAttribute("ref", ref);
5863
5863
  }
5864
- assignHeadlessInstance(element, contractName, ref, parentCoord, options);
5864
+ assignHeadlessInstance(
5865
+ element,
5866
+ contractName,
5867
+ ref,
5868
+ parentCoord,
5869
+ options,
5870
+ slowForEachPrefix
5871
+ );
5865
5872
  continue;
5866
5873
  }
5867
5874
  }
@@ -5872,7 +5879,7 @@ function walkChildren(parent, parentCoord, options, scope) {
5872
5879
  const coord2 = `${parentCoord}/${scope.childCounter}`;
5873
5880
  element.setAttribute(COORD_ATTR$1, coord2);
5874
5881
  scope.childCounter++;
5875
- walkForEachChildren(element, `$${trackBy}`, options);
5882
+ walkForEachChildren(element, `$${trackBy}`, options, slowForEachPrefix);
5876
5883
  continue;
5877
5884
  }
5878
5885
  }
@@ -5880,18 +5887,19 @@ function walkChildren(parent, parentCoord, options, scope) {
5880
5887
  if (slowForEachAttr) {
5881
5888
  const jayTrackBy = element.getAttribute("jayTrackBy");
5882
5889
  if (jayTrackBy) {
5883
- element.setAttribute(COORD_ATTR$1, jayTrackBy);
5884
- walkChildren(element, jayTrackBy, options, newScope());
5890
+ const coord2 = slowForEachPrefix ? `${slowForEachPrefix}/${jayTrackBy}` : jayTrackBy;
5891
+ element.setAttribute(COORD_ATTR$1, coord2);
5892
+ walkChildren(element, coord2, options, newScope(), coord2);
5885
5893
  continue;
5886
5894
  }
5887
5895
  }
5888
5896
  const coord = `${parentCoord}/${scope.childCounter}`;
5889
5897
  element.setAttribute(COORD_ATTR$1, coord);
5890
5898
  scope.childCounter++;
5891
- walkChildren(element, coord, options, newScope());
5899
+ walkChildren(element, coord, options, newScope(), slowForEachPrefix);
5892
5900
  }
5893
5901
  }
5894
- function assignHeadlessInstance(element, contractName, ref, parentCoord, options) {
5902
+ function assignHeadlessInstance(element, contractName, ref, parentCoord, options, slowForEachPrefix = "") {
5895
5903
  const instanceCoord = `${parentCoord}/${contractName}:${ref}`;
5896
5904
  element.setAttribute(COORD_ATTR$1, instanceCoord);
5897
5905
  const significantChildren = element.childNodes.filter(
@@ -5904,9 +5912,9 @@ function assignHeadlessInstance(element, contractName, ref, parentCoord, options
5904
5912
  children.forEach((child) => wrapper.appendChild(child));
5905
5913
  element.appendChild(wrapper);
5906
5914
  }
5907
- walkChildren(element, instanceCoord, options, newScope());
5915
+ walkChildren(element, instanceCoord, options, newScope(), slowForEachPrefix);
5908
5916
  }
5909
- function walkForEachChildren(parent, itemPrefix, options) {
5917
+ function walkForEachChildren(parent, itemPrefix, options, slowForEachPrefix = "") {
5910
5918
  const scope = newScope();
5911
5919
  for (const child of parent.childNodes) {
5912
5920
  if (child.nodeType !== NodeType.ELEMENT_NODE)
@@ -5924,16 +5932,27 @@ function walkForEachChildren(parent, itemPrefix, options) {
5924
5932
  ref = `AR${idx}`;
5925
5933
  element.setAttribute("ref", ref);
5926
5934
  }
5927
- const instanceCoord = `${itemPrefix}/${contractName}:${ref}`;
5935
+ const instanceCoord = itemPrefix ? `${itemPrefix}/${contractName}:${ref}` : `${contractName}:${ref}`;
5928
5936
  element.setAttribute(COORD_ATTR$1, instanceCoord);
5929
- walkChildren(element, instanceCoord, options, newScope());
5937
+ walkChildren(element, instanceCoord, options, newScope(), slowForEachPrefix);
5938
+ continue;
5939
+ }
5940
+ }
5941
+ const forEachAttr = element.getAttribute("forEach");
5942
+ if (forEachAttr) {
5943
+ const trackBy = element.getAttribute("trackBy");
5944
+ if (trackBy) {
5945
+ const coord2 = itemPrefix ? `${itemPrefix}/${scope.childCounter}` : `${scope.childCounter}`;
5946
+ element.setAttribute(COORD_ATTR$1, coord2);
5947
+ scope.childCounter++;
5948
+ walkForEachChildren(element, null, options, slowForEachPrefix);
5930
5949
  continue;
5931
5950
  }
5932
5951
  }
5933
- const coord = `${itemPrefix}/${scope.childCounter}`;
5952
+ const coord = itemPrefix ? `${itemPrefix}/${scope.childCounter}` : `${scope.childCounter}`;
5934
5953
  element.setAttribute(COORD_ATTR$1, coord);
5935
5954
  scope.childCounter++;
5936
- walkChildren(element, coord, options, newScope());
5955
+ walkChildren(element, coord, options, newScope(), slowForEachPrefix);
5937
5956
  }
5938
5957
  }
5939
5958
  var ContractTagType = /* @__PURE__ */ ((ContractTagType2) => {
@@ -16870,6 +16889,231 @@ async function parseHeadlessImports(elements, validations, filePath, importResol
16870
16889
  }
16871
16890
  return result;
16872
16891
  }
16892
+ function injectHeadfullFSTemplates(html2, sourceDir, importResolver) {
16893
+ const root = parse_2(html2);
16894
+ const allHeadfullElements = root.querySelectorAll('script[type="application/jay-headfull"]');
16895
+ const fsElements = allHeadfullElements.filter((el) => el.getAttribute("contract"));
16896
+ if (fsElements.length === 0)
16897
+ return html2;
16898
+ const body = root.querySelector("body");
16899
+ if (!body)
16900
+ return html2;
16901
+ for (const element of fsElements) {
16902
+ const src = element.getAttribute("src");
16903
+ const rawNames = element.getAttribute("names");
16904
+ if (!src || !rawNames)
16905
+ continue;
16906
+ const names = parseImportNames(rawNames);
16907
+ if (names.length === 0)
16908
+ continue;
16909
+ const contractName = (names[0].as || names[0].name).toLowerCase();
16910
+ const jayHtmlContent = importResolver.readJayHtml(sourceDir, src);
16911
+ if (!jayHtmlContent)
16912
+ continue;
16913
+ const jayHtmlRoot = parse_2(jayHtmlContent);
16914
+ const jayHtmlBody = jayHtmlRoot.querySelector("body");
16915
+ if (!jayHtmlBody)
16916
+ continue;
16917
+ const jayTags = body.querySelectorAll("*").filter((el) => el.tagName?.toLowerCase() === `jay:${contractName}`);
16918
+ for (const jayTag of jayTags) {
16919
+ if (!jayTag.innerHTML.trim()) {
16920
+ jayTag.set_content(jayHtmlBody.innerHTML);
16921
+ }
16922
+ }
16923
+ }
16924
+ return root.toString();
16925
+ }
16926
+ async function parseHeadfullFSImports(elements, validations, filePath, importResolver, body, projectRoot) {
16927
+ const headlessImports = [];
16928
+ const cssParts = [];
16929
+ const linkedCssFiles = [];
16930
+ for (const element of elements) {
16931
+ const src = element.getAttribute("src");
16932
+ const contractAttr = element.getAttribute("contract");
16933
+ const rawNames = element.getAttribute("names");
16934
+ if (!src) {
16935
+ validations.push("headfull FS import must specify src attribute");
16936
+ continue;
16937
+ }
16938
+ if (!contractAttr) {
16939
+ continue;
16940
+ }
16941
+ if (!rawNames) {
16942
+ validations.push(`headfull FS import for module ${src} must specify names attribute`);
16943
+ continue;
16944
+ }
16945
+ const names = parseImportNames(rawNames);
16946
+ if (names.length === 0) {
16947
+ validations.push(
16948
+ `headfull FS import for module ${src} does not specify what to import`
16949
+ );
16950
+ continue;
16951
+ }
16952
+ const componentExportName = names[0].name;
16953
+ const contractName = (names[0].as || componentExportName).toLowerCase();
16954
+ let contractPath = path.resolve(filePath, contractAttr);
16955
+ let loadedContract;
16956
+ try {
16957
+ const contractResult = importResolver.loadContract(contractPath);
16958
+ validations.push(...contractResult.validations);
16959
+ if (!contractResult.val) {
16960
+ continue;
16961
+ }
16962
+ loadedContract = contractResult.val;
16963
+ } catch (e2) {
16964
+ if (projectRoot && projectRoot !== filePath) {
16965
+ try {
16966
+ contractPath = path.resolve(projectRoot, contractAttr);
16967
+ const fallbackResult = importResolver.loadContract(contractPath);
16968
+ validations.push(...fallbackResult.validations);
16969
+ if (!fallbackResult.val) {
16970
+ continue;
16971
+ }
16972
+ loadedContract = fallbackResult.val;
16973
+ } catch (e22) {
16974
+ validations.push(
16975
+ `Failed to load contract for headfull FS component ${src}: ${e22.message}`
16976
+ );
16977
+ continue;
16978
+ }
16979
+ } else {
16980
+ validations.push(
16981
+ `Failed to load contract for headfull FS component ${src}: ${e2.message}`
16982
+ );
16983
+ continue;
16984
+ }
16985
+ }
16986
+ let jayHtmlContent = importResolver.readJayHtml(filePath, src);
16987
+ if (jayHtmlContent === null && projectRoot && projectRoot !== filePath) {
16988
+ jayHtmlContent = importResolver.readJayHtml(projectRoot, src);
16989
+ }
16990
+ if (jayHtmlContent === null) {
16991
+ validations.push(
16992
+ `jay-html file not found for headfull FS component ${src} (expected ${src}.jay-html)`
16993
+ );
16994
+ continue;
16995
+ }
16996
+ const jayHtmlRoot = parse_2(jayHtmlContent);
16997
+ const jayHtmlBody = jayHtmlRoot.querySelector("body");
16998
+ if (!jayHtmlBody) {
16999
+ validations.push(`headfull FS component ${src} jay-html must have a body tag`);
17000
+ continue;
17001
+ }
17002
+ const componentDir = path.dirname(path.resolve(filePath, src));
17003
+ const componentCssResult = await extractCss(jayHtmlRoot, componentDir);
17004
+ validations.push(...componentCssResult.validations);
17005
+ if (componentCssResult.val?.css) {
17006
+ cssParts.push(componentCssResult.val.css);
17007
+ }
17008
+ if (componentCssResult.val?.linkedCssFiles) {
17009
+ linkedCssFiles.push(...componentCssResult.val.linkedCssFiles);
17010
+ }
17011
+ const jayTags = body.querySelectorAll("*").filter((el) => el.tagName?.toLowerCase() === `jay:${contractName}`);
17012
+ for (const jayTag of jayTags) {
17013
+ const existingContent = jayTag.innerHTML.trim();
17014
+ if (existingContent) {
17015
+ continue;
17016
+ }
17017
+ jayTag.set_content(jayHtmlBody.innerHTML);
17018
+ }
17019
+ try {
17020
+ const contractTypes = await contractToImportsViewStateAndRefs(
17021
+ loadedContract,
17022
+ contractPath,
17023
+ importResolver
17024
+ );
17025
+ contractTypes.map(({ type: type2, refs: subContractRefsTree, enumsToImport }) => {
17026
+ const contractInternalName = loadedContract.name;
17027
+ const refsTypeName = `${pascalCase(contractInternalName)}Refs`;
17028
+ const repeatedRefsTypeName = `${pascalCase(contractInternalName)}RepeatedRefs`;
17029
+ const refs = mkRefsTree(
17030
+ subContractRefsTree.refs,
17031
+ subContractRefsTree.children,
17032
+ subContractRefsTree.repeated,
17033
+ refsTypeName,
17034
+ repeatedRefsTypeName
17035
+ );
17036
+ const relativeContractPath = path.relative(filePath, contractPath);
17037
+ const enumsToImportRelativeToJayHtml = enumsToImport.map(
17038
+ (enumToImport) => ({
17039
+ type: enumToImport.type,
17040
+ declaringModule: path.relative(filePath, enumToImport.declaringModule)
17041
+ })
17042
+ );
17043
+ const enumsFromContract = enumsToImportRelativeToJayHtml.filter((_) => _.declaringModule === relativeContractPath).map((_) => _.type);
17044
+ const nestedTypeNames = collectNestedTypeNames(type2);
17045
+ const nestedTypeImports = nestedTypeNames.filter((name) => name !== type2.name).map((name) => ({ name, type: JayUnknown }));
17046
+ const contractLink = {
17047
+ module: relativeContractPath,
17048
+ names: [
17049
+ { name: type2.name, type: type2 },
17050
+ { name: refsTypeName, type: JayUnknown },
17051
+ ...nestedTypeImports,
17052
+ ...enumsFromContract.map((_) => ({ name: _.name, type: _ }))
17053
+ ]
17054
+ };
17055
+ const enumsFromOtherContracts = enumsToImportRelativeToJayHtml.filter(
17056
+ (_) => _.declaringModule !== relativeContractPath
17057
+ );
17058
+ const enumImportLinks = Object.entries(
17059
+ enumsFromOtherContracts.reduce(
17060
+ (acc, enumToImport) => {
17061
+ const module = enumToImport.declaringModule;
17062
+ if (!acc[module]) {
17063
+ acc[module] = [];
17064
+ }
17065
+ acc[module].push(enumToImport);
17066
+ return acc;
17067
+ },
17068
+ {}
17069
+ )
17070
+ ).map(([module, enums]) => ({
17071
+ module,
17072
+ names: enums.map((enumToImport) => ({
17073
+ name: enumToImport.type.name,
17074
+ type: enumToImport.type
17075
+ }))
17076
+ }));
17077
+ const contractLinks = [contractLink, ...enumImportLinks];
17078
+ const moduleResolveDir = projectRoot && projectRoot !== filePath ? projectRoot : filePath;
17079
+ let relativeModule = path.relative(
17080
+ filePath,
17081
+ importResolver.resolveLink(moduleResolveDir, src)
17082
+ );
17083
+ if (!relativeModule.startsWith(".")) {
17084
+ relativeModule = "./" + relativeModule;
17085
+ }
17086
+ const codeLink = {
17087
+ module: relativeModule,
17088
+ names: [
17089
+ {
17090
+ name: componentExportName,
17091
+ type: new JayComponentType(componentExportName, [])
17092
+ }
17093
+ ]
17094
+ };
17095
+ headlessImports.push({
17096
+ contractName,
17097
+ refs,
17098
+ rootType: type2,
17099
+ contractLinks,
17100
+ codeLink,
17101
+ contract: loadedContract,
17102
+ contractPath
17103
+ });
17104
+ });
17105
+ } catch (e2) {
17106
+ validations.push(
17107
+ `failed to parse contract for headfull FS component ${src} - ${e2.message}${e2.stack}`
17108
+ );
17109
+ }
17110
+ }
17111
+ return {
17112
+ headlessImports,
17113
+ css: cssParts.length > 0 ? cssParts.join("\n\n") : void 0,
17114
+ linkedCssFiles
17115
+ };
17116
+ }
16873
17117
  function normalizeFilename(filename) {
16874
17118
  return filename.replace(".jay-html", "");
16875
17119
  }
@@ -16967,13 +17211,31 @@ async function parseJayFile(html2, filename, filePath, options, linkedContractRe
16967
17211
  const { val: jayYaml, validations } = parseYaml(root);
16968
17212
  if (validations.length > 0)
16969
17213
  return new WithValidations(void 0, validations);
17214
+ const allHeadfullElements = root.querySelectorAll('script[type="application/jay-headfull"]');
17215
+ const regularHeadfullElements = allHeadfullElements.filter(
17216
+ (el) => !el.getAttribute("contract")
17217
+ );
17218
+ const fsHeadfullElements = allHeadfullElements.filter((el) => el.getAttribute("contract"));
16970
17219
  const headfullImports = parseHeadfullImports(
16971
- root.querySelectorAll('script[type="application/jay-headfull"]'),
17220
+ regularHeadfullElements,
16972
17221
  validations,
16973
17222
  filePath,
16974
17223
  options,
16975
17224
  linkedContractResolver
16976
17225
  );
17226
+ let body = root.querySelector("body");
17227
+ if (body === null) {
17228
+ validations.push(`jay file must have exactly a body tag`);
17229
+ return new WithValidations(void 0, validations);
17230
+ }
17231
+ const headfullFSResult = await parseHeadfullFSImports(
17232
+ fsHeadfullElements,
17233
+ validations,
17234
+ filePath,
17235
+ linkedContractResolver,
17236
+ body,
17237
+ projectRoot
17238
+ );
16977
17239
  const headlessImports = await parseHeadlessImports(
16978
17240
  root.querySelectorAll('script[type="application/jay-headless"]'),
16979
17241
  validations,
@@ -16981,13 +17243,14 @@ async function parseJayFile(html2, filename, filePath, options, linkedContractRe
16981
17243
  linkedContractResolver,
16982
17244
  projectRoot
16983
17245
  );
17246
+ const allHeadlessImports = [...headlessImports, ...headfullFSResult.headlessImports];
16984
17247
  const importNames = headfullImports.flatMap((_) => _.names);
16985
17248
  const types2 = await parseTypes(
16986
17249
  jayYaml,
16987
17250
  validations,
16988
17251
  baseElementName,
16989
17252
  importNames,
16990
- headlessImports,
17253
+ allHeadlessImports,
16991
17254
  filePath,
16992
17255
  linkedContractResolver
16993
17256
  );
@@ -16996,7 +17259,7 @@ async function parseJayFile(html2, filename, filePath, options, linkedContractRe
16996
17259
  );
16997
17260
  const imports = [
16998
17261
  ...headfullImports,
16999
- ...headlessImports.flatMap((_) => [
17262
+ ...allHeadlessImports.flatMap((_) => [
17000
17263
  ..._.contractLinks,
17001
17264
  ...usedAsInstance.has(_.contractName) ? [_.codeLink] : []
17002
17265
  ])
@@ -17007,14 +17270,17 @@ async function parseJayFile(html2, filename, filePath, options, linkedContractRe
17007
17270
  validations.push(...cssResult.validations);
17008
17271
  if (validations.length > 0)
17009
17272
  return new WithValidations(void 0, validations);
17010
- let body = root.querySelector("body");
17011
- if (body === null) {
17012
- validations.push(`jay file must have exactly a body tag`);
17013
- return new WithValidations(void 0, validations);
17273
+ let css = cssResult.val?.css;
17274
+ if (headfullFSResult.css) {
17275
+ css = css ? css + "\n\n" + headfullFSResult.css : headfullFSResult.css;
17276
+ }
17277
+ let allLinkedCssFiles = cssResult.val?.linkedCssFiles || [];
17278
+ if (headfullFSResult.linkedCssFiles.length > 0) {
17279
+ allLinkedCssFiles = [...allLinkedCssFiles, ...headfullFSResult.linkedCssFiles];
17014
17280
  }
17015
17281
  const { serverTrackByMap, clientTrackByMap } = extractTrackByMaps(
17016
17282
  jayYaml.parsedContract,
17017
- headlessImports
17283
+ allHeadlessImports
17018
17284
  );
17019
17285
  return new WithValidations(
17020
17286
  {
@@ -17024,10 +17290,10 @@ async function parseJayFile(html2, filename, filePath, options, linkedContractRe
17024
17290
  body,
17025
17291
  baseElementName,
17026
17292
  namespaces,
17027
- headlessImports,
17293
+ headlessImports: allHeadlessImports,
17028
17294
  headLinks,
17029
- css: cssResult.val?.css,
17030
- linkedCssFiles: cssResult.val?.linkedCssFiles.length > 0 ? cssResult.val.linkedCssFiles : void 0,
17295
+ css,
17296
+ linkedCssFiles: allLinkedCssFiles.length > 0 ? allLinkedCssFiles : void 0,
17031
17297
  filename: normalizedFileName,
17032
17298
  contract: jayYaml.parsedContract,
17033
17299
  contractRef: jayYaml.contractRef,
@@ -17368,7 +17634,14 @@ ${propLines.join("\n")}
17368
17634
  }
17369
17635
  function generateParamsInterface(contractName, params) {
17370
17636
  const paramsTypeName = `${contractName}Params`;
17371
- const paramLines = params.map((param) => ` ${camelCase$1(param.name)}: string;`);
17637
+ const paramLines = params.map((param) => {
17638
+ const name = camelCase$1(param.name);
17639
+ if (param.kind === "optional")
17640
+ return ` ${name}?: string;`;
17641
+ if (param.kind === "catch-all")
17642
+ return ` ${name}: string[];`;
17643
+ return ` ${name}: string;`;
17644
+ });
17372
17645
  const paramsImport = `import { UrlParams } from '${JAY_FULLSTACK_COMPONENTS}';`;
17373
17646
  const paramsInterface = `export interface ${paramsTypeName} extends UrlParams {
17374
17647
  ${paramLines.join("\n")}
@@ -17721,7 +17994,10 @@ function parseContract(contractYaml, fileName) {
17721
17994
  }
17722
17995
  let parsedParams;
17723
17996
  if (parsedYaml.params && typeof parsedYaml.params === "object" && !Array.isArray(parsedYaml.params)) {
17724
- parsedParams = Object.keys(parsedYaml.params).map((name) => ({ name }));
17997
+ parsedParams = Object.entries(parsedYaml.params).map(([name, value]) => ({
17998
+ name,
17999
+ kind: typeof value === "string" && value.endsWith("?") ? "optional" : typeof value === "string" && value.endsWith("[]") ? "catch-all" : "required"
18000
+ }));
17725
18001
  if (parsedParams.length === 0)
17726
18002
  parsedParams = void 0;
17727
18003
  }
@@ -19178,9 +19454,12 @@ ${indent.curr}return ${childElement.rendered}}, '${trackBy}')`,
19178
19454
  }
19179
19455
  };
19180
19456
  collectContractRefs(headlessImport.refs);
19457
+ const childImportedSymbols = new Set(newContext.importedSymbols);
19458
+ childImportedSymbols.delete(pluginComponentName);
19181
19459
  const childContext = {
19182
19460
  ...newContext,
19183
19461
  variables: componentVariables,
19462
+ importedSymbols: childImportedSymbols,
19184
19463
  indent: childIndent,
19185
19464
  importedRefNameToRef: instanceRefMap,
19186
19465
  recursiveRegions: [],
@@ -20347,30 +20626,81 @@ ${indent.firstLine})`,
20347
20626
  const itemTypeName = slowForEachVariables.currentType.name;
20348
20627
  const paramName = arrayAccessor.rootVar;
20349
20628
  const getItemsFragment = arrayAccessor.render().map((_) => `(${paramName}: ${parentTypeName}) => ${_}`);
20629
+ const accumulatedJayTrackBy = context.slowForEachJayTrackBy ? `${context.slowForEachJayTrackBy}/${jayTrackBy}` : jayTrackBy;
20350
20630
  const itemContext = {
20351
20631
  ...context,
20352
20632
  variables: slowForEachVariables,
20353
20633
  indent: indent.child().child(),
20354
20634
  dynamicRef: true,
20355
- insideSlowForEach: true
20635
+ insideSlowForEach: true,
20636
+ slowForEachJayTrackBy: accumulatedJayTrackBy
20356
20637
  };
20357
- const renderContext2 = buildRenderContext(itemContext);
20358
- const childContent = renderHydrateElementContent(
20359
- element,
20360
- itemContext,
20361
- renderContext2,
20362
- null
20638
+ buildRenderContext(itemContext);
20639
+ const itemChildNodes = element.childNodes.filter(
20640
+ (_) => _.nodeType !== NodeType.TEXT_NODE || (_.innerText || "").trim() !== ""
20363
20641
  );
20364
- if (childContent.rendered.trim().length === 0) {
20642
+ const childFragments = itemChildNodes.map((child) => renderHydrateNode(child, itemContext));
20643
+ const nonEmptyChildren = childFragments.filter((f) => f.rendered.trim());
20644
+ if (nonEmptyChildren.length === 0) {
20365
20645
  return RenderFragment.empty();
20366
20646
  }
20647
+ let callbackBody;
20648
+ let callbackImports = Imports.none();
20649
+ let callbackValidations = [];
20650
+ let callbackRefs;
20651
+ if (nonEmptyChildren.length === 1) {
20652
+ callbackBody = nonEmptyChildren[0].rendered.trim();
20653
+ callbackImports = nonEmptyChildren[0].imports;
20654
+ callbackValidations = [...nonEmptyChildren[0].validations];
20655
+ callbackRefs = nonEmptyChildren[0].refs;
20656
+ } else {
20657
+ const hasDynamicGroups = itemChildNodes.some(
20658
+ (child) => child.nodeType === NodeType.ELEMENT_NODE && (isConditional(child) && conditionIsInteractive(
20659
+ child.getAttribute("if"),
20660
+ itemContext.interactivePaths
20661
+ ) || isForEach(child) || isSlowForEach(child))
20662
+ );
20663
+ if (hasDynamicGroups) {
20664
+ const childParts = [];
20665
+ for (let i = 0; i < childFragments.length; i++) {
20666
+ const frag = childFragments[i];
20667
+ if (frag.rendered.trim()) {
20668
+ childParts.push(frag.rendered);
20669
+ callbackImports = callbackImports.plus(frag.imports);
20670
+ callbackValidations.push(...frag.validations);
20671
+ if (frag.refs)
20672
+ callbackRefs = callbackRefs ? mergeRefsTrees(callbackRefs, frag.refs) : frag.refs;
20673
+ } else if (itemChildNodes[i].nodeType === NodeType.ELEMENT_NODE) {
20674
+ childParts.push(`${indent.firstLine} STATIC`);
20675
+ callbackImports = callbackImports.plus(Import.STATIC);
20676
+ }
20677
+ }
20678
+ const childrenArr = childParts.join(",\n");
20679
+ callbackBody = `adoptDynamicElement('', {}, [
20680
+ ${childrenArr},
20681
+ ${indent.firstLine} ])`;
20682
+ callbackImports = callbackImports.plus(Import.adoptDynamicElement);
20683
+ } else {
20684
+ const childrenArr = nonEmptyChildren.map((f) => f.rendered).join(",\n");
20685
+ callbackBody = `adoptElement('', {}, [
20686
+ ${childrenArr},
20687
+ ${indent.firstLine} ])`;
20688
+ for (const f of nonEmptyChildren) {
20689
+ callbackImports = callbackImports.plus(f.imports);
20690
+ callbackValidations.push(...f.validations);
20691
+ if (f.refs)
20692
+ callbackRefs = callbackRefs ? mergeRefsTrees(callbackRefs, f.refs) : f.refs;
20693
+ }
20694
+ callbackImports = callbackImports.plus(Import.adoptElement);
20695
+ }
20696
+ }
20367
20697
  const slowForEachFragment = new RenderFragment(
20368
20698
  `${indent.firstLine}slowForEachItem<${parentTypeName}, ${itemTypeName}>(${getItemsFragment.rendered}, ${jayIndex}, '${jayTrackBy}',
20369
- ${indent.firstLine}() => ${childContent.rendered.trim()}
20699
+ ${indent.firstLine}() => ${callbackBody}
20370
20700
  ${indent.firstLine})`,
20371
- Imports.for(Import.slowForEachItem).plus(getItemsFragment.imports).plus(childContent.imports),
20372
- [...getItemsFragment.validations, ...childContent.validations],
20373
- childContent.refs
20701
+ Imports.for(Import.slowForEachItem).plus(getItemsFragment.imports).plus(callbackImports),
20702
+ [...getItemsFragment.validations, ...callbackValidations],
20703
+ callbackRefs
20374
20704
  );
20375
20705
  return nestRefs(arrayName.split("."), slowForEachFragment);
20376
20706
  }
@@ -20716,6 +21046,13 @@ function renderHydrateElementContent(element, context, renderContext, coordinate
20716
21046
  const slashIndex = coordinate.indexOf("/");
20717
21047
  coordinate = slashIndex >= 0 ? coordinate.slice(slashIndex + 1) : "0";
20718
21048
  }
21049
+ if (context.slowForEachJayTrackBy && coordTemplate) {
21050
+ if (coordinate === context.slowForEachJayTrackBy) {
21051
+ coordinate = "";
21052
+ } else if (coordinate.startsWith(context.slowForEachJayTrackBy + "/")) {
21053
+ coordinate = coordinate.slice(context.slowForEachJayTrackBy.length + 1);
21054
+ }
21055
+ }
20719
21056
  const renderedRef = renderElementRef$1(element, renderContext);
20720
21057
  if (hasInteractiveChildren) {
20721
21058
  const childParts = [];
@@ -20817,6 +21154,7 @@ function renderHydrateElementContent(element, context, renderContext, coordinate
20817
21154
  let childrenRendered = "[]";
20818
21155
  let childImports = Imports.none();
20819
21156
  let childValidations = [];
21157
+ let childRefs;
20820
21158
  if (textFragment) {
20821
21159
  const accessor = textFragment.rendered.replace(/^dt\(/, "").replace(/\)$/, "");
20822
21160
  childrenRendered = `[adoptText("${coordinate}", ${accessor})]`;
@@ -20824,13 +21162,24 @@ function renderHydrateElementContent(element, context, renderContext, coordinate
20824
21162
  textFragment.imports.minus(Import.dynamicText)
20825
21163
  );
20826
21164
  childValidations = textFragment.validations;
21165
+ } else {
21166
+ const children = mergeHydrateFragments(
21167
+ childNodes.map((child) => renderHydrateNode(child, context)),
21168
+ ",\n"
21169
+ );
21170
+ if (children.rendered.trim()) {
21171
+ childrenRendered = `[${children.rendered}]`;
21172
+ childImports = children.imports;
21173
+ childValidations = children.validations;
21174
+ childRefs = children.refs;
21175
+ }
20827
21176
  }
20828
21177
  const refSuffix2 = renderedRef.rendered ? `, ${renderedRef.rendered}` : "";
20829
21178
  return new RenderFragment(
20830
21179
  `${indent.firstLine}adoptElement("${coordinate}", ${attributes2.rendered}, ${childrenRendered}${refSuffix2})`,
20831
21180
  Imports.for(Import.adoptElement).plus(attributes2.imports).plus(childImports).plus(renderedRef.imports),
20832
21181
  [...attributes2.validations, ...childValidations, ...renderedRef.validations],
20833
- renderedRef.refs
21182
+ mergeRefsTrees(...[childRefs, renderedRef.refs].filter(Boolean))
20834
21183
  );
20835
21184
  }
20836
21185
  const childFragments = mergeHydrateFragments(
@@ -21043,15 +21392,23 @@ ${indent.firstLine}}`,
21043
21392
  const arrayExpr = forEachAccessor.render().rendered;
21044
21393
  const itemIndent = new Indent(indent.curr + " ");
21045
21394
  const trackByExpr = `${forEachVariables.currentVar}.${trackBy}`;
21395
+ const slowPrefix = context.slowForEachCoordPrefix ? `'${context.slowForEachCoordPrefix}'` : void 0;
21396
+ const ancestorPrefix = context.forEachAccumulatedPrefix ?? slowPrefix;
21397
+ const currentTrackByPrefix = `escapeAttr(String(${trackByExpr}))`;
21398
+ const accumulatedPrefix = ancestorPrefix ? `${ancestorPrefix} + '/' + ${currentTrackByPrefix}` : currentTrackByPrefix;
21046
21399
  const itemContext = {
21047
21400
  ...context,
21048
21401
  variables: forEachVariables,
21049
21402
  indent: itemIndent,
21050
21403
  varMappings: { ...context.varMappings, [trackBy]: trackByExpr },
21051
- insideForEach: true
21404
+ insideForEach: true,
21405
+ forEachAccumulatedPrefix: accumulatedPrefix,
21406
+ forEachAncestorPrefix: ancestorPrefix,
21407
+ slowForEachCoordPrefix: void 0
21408
+ // consumed by ancestorPrefix computation
21052
21409
  };
21053
21410
  const openTag = renderServerOpenTag(element, itemContext, null);
21054
- const itemRootCoordExpr = `escapeAttr(String(${trackByExpr}))`;
21411
+ const itemRootCoordExpr = accumulatedPrefix;
21055
21412
  const coordinateW = w(
21056
21413
  itemIndent,
21057
21414
  `' jay-coordinate="' + ${itemRootCoordExpr} + '">'`,
@@ -21076,16 +21433,44 @@ ${indent.firstLine}}`,
21076
21433
  if (isSlowForEach(element)) {
21077
21434
  const slowForEachInfo = getSlowForEachInfo(element);
21078
21435
  if (slowForEachInfo) {
21079
- const slowForEachVariables = variables.childVariableFor(
21080
- parseAccessor(slowForEachInfo.arrayName, variables)
21081
- );
21436
+ const { arrayName, jayIndex, jayTrackBy } = slowForEachInfo;
21437
+ const arrayAccessor = parseAccessor(arrayName, variables);
21438
+ const slowForEachVariables = variables.childVariableFor(arrayAccessor);
21439
+ const arrayExpr = arrayAccessor.render().rendered;
21440
+ const itemVar = slowForEachVariables.currentVar;
21441
+ const slowCoordPrefix = context.slowForEachCoordPrefix ? `${context.slowForEachCoordPrefix}/${jayTrackBy}` : jayTrackBy;
21082
21442
  const itemContext = {
21083
21443
  ...context,
21084
21444
  variables: slowForEachVariables,
21085
21445
  indent,
21086
- insideSlowForEach: true
21446
+ insideSlowForEach: true,
21447
+ slowForEachCoordPrefix: slowCoordPrefix
21087
21448
  };
21088
- return renderServerElementContent(element, itemContext);
21449
+ const childContent = renderServerElementContent(element, itemContext, {
21450
+ isRoot: true
21451
+ });
21452
+ const needsItemVar = childContent.rendered.includes(itemVar + ".");
21453
+ if (needsItemVar) {
21454
+ const itemIndent = new Indent(indent.curr + " ");
21455
+ const indentedContext = {
21456
+ ...context,
21457
+ variables: slowForEachVariables,
21458
+ indent: itemIndent,
21459
+ insideSlowForEach: true,
21460
+ slowForEachCoordPrefix: slowCoordPrefix
21461
+ };
21462
+ const indentedContent = renderServerElementContent(element, indentedContext, {
21463
+ isRoot: true
21464
+ });
21465
+ return new RenderFragment(
21466
+ `${indent.firstLine}{ const ${itemVar} = ${arrayExpr}?.[${jayIndex}]; if (${itemVar}) {
21467
+ ${indentedContent.rendered}
21468
+ ${indent.firstLine}}}`,
21469
+ indentedContent.imports,
21470
+ [...arrayAccessor.validations, ...indentedContent.validations]
21471
+ );
21472
+ }
21473
+ return childContent;
21089
21474
  }
21090
21475
  }
21091
21476
  return renderServerElementContent(element, context);
@@ -21525,16 +21910,36 @@ function renderServerElementContent(element, context, options) {
21525
21910
  }
21526
21911
  if (coordTemplate !== null) {
21527
21912
  if (isStaticCoordinate(coordTemplate)) {
21528
- parts.push(w(indent, `' jay-coordinate="${coordTemplate}">'`));
21913
+ if (context.forEachAccumulatedPrefix) {
21914
+ parts.push(
21915
+ w(
21916
+ indent,
21917
+ `' jay-coordinate="' + ${context.forEachAccumulatedPrefix} + '/${coordTemplate}">'`,
21918
+ Imports.for(Import.escapeAttr)
21919
+ )
21920
+ );
21921
+ } else {
21922
+ parts.push(w(indent, `' jay-coordinate="${coordTemplate}">'`));
21923
+ }
21529
21924
  } else {
21530
21925
  const coordExpr = compileCoordinateExpr(coordTemplate, context.varMappings);
21531
- parts.push(
21532
- w(
21533
- indent,
21534
- `' jay-coordinate="' + ${coordExpr} + '">'`,
21535
- Imports.for(Import.escapeAttr)
21536
- )
21537
- );
21926
+ if (context.forEachAncestorPrefix) {
21927
+ parts.push(
21928
+ w(
21929
+ indent,
21930
+ `' jay-coordinate="' + ${context.forEachAncestorPrefix} + '/' + ${coordExpr} + '">'`,
21931
+ Imports.for(Import.escapeAttr)
21932
+ )
21933
+ );
21934
+ } else {
21935
+ parts.push(
21936
+ w(
21937
+ indent,
21938
+ `' jay-coordinate="' + ${coordExpr} + '">'`,
21939
+ Imports.for(Import.escapeAttr)
21940
+ )
21941
+ );
21942
+ }
21538
21943
  }
21539
21944
  } else {
21540
21945
  parts.push(w(indent, `'>'`));
@@ -29060,6 +29465,15 @@ const JAY_IMPORT_RESOLVER = {
29060
29465
  },
29061
29466
  resolvePluginManifest(pluginName, projectRoot) {
29062
29467
  return resolvePluginManifest(projectRoot, pluginName);
29468
+ },
29469
+ readJayHtml(importingModuleDir, src) {
29470
+ const resolvedPath = src.startsWith(".") ? path.resolve(importingModuleDir, src) : src;
29471
+ const jayHtmlPath = resolvedPath + ".jay-html";
29472
+ try {
29473
+ return fs$1.readFileSync(jayHtmlPath, "utf-8");
29474
+ } catch {
29475
+ return null;
29476
+ }
29063
29477
  }
29064
29478
  };
29065
29479
  function buildPhaseMap(contract, headlessContracts, importResolver) {
@@ -29284,7 +29698,7 @@ function resolveRelativePaths(root, sourceDir) {
29284
29698
  const scripts = root.querySelectorAll("script[src]");
29285
29699
  for (const script of scripts) {
29286
29700
  const scriptType = script.getAttribute("type");
29287
- if (scriptType === "application/jay-data" || scriptType === "application/jay-headless") {
29701
+ if (scriptType === "application/jay-data" || scriptType === "application/jay-headless" || scriptType === "application/jay-headfull") {
29288
29702
  continue;
29289
29703
  }
29290
29704
  const src = script.getAttribute("src");
@@ -29574,6 +29988,7 @@ export {
29574
29988
  getJayHtmlImports,
29575
29989
  getLinkedContractDir,
29576
29990
  hasSlowPhaseProperties,
29991
+ injectHeadfullFSTemplates,
29577
29992
  isTagInPhase,
29578
29993
  loadLinkedContract,
29579
29994
  parseAction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/compiler-jay-html",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
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.14.0",
38
- "@jay-framework/compiler-shared": "^0.14.0",
39
- "@jay-framework/component": "^0.14.0",
40
- "@jay-framework/logger": "^0.14.0",
41
- "@jay-framework/runtime": "^0.14.0",
42
- "@jay-framework/secure": "^0.14.0",
37
+ "@jay-framework/compiler-analyze-exported-types": "^0.15.1",
38
+ "@jay-framework/compiler-shared": "^0.15.1",
39
+ "@jay-framework/component": "^0.15.1",
40
+ "@jay-framework/logger": "^0.15.1",
41
+ "@jay-framework/runtime": "^0.15.1",
42
+ "@jay-framework/secure": "^0.15.1",
43
43
  "@types/js-yaml": "^4.0.9",
44
44
  "change-case": "^4.1.2",
45
45
  "js-yaml": "^4.1.0",
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@caiogondim/strip-margin": "^1.0.0",
54
- "@jay-framework/4-react": "^0.14.0",
55
- "@jay-framework/dev-environment": "^0.14.0",
54
+ "@jay-framework/4-react": "^0.15.1",
55
+ "@jay-framework/dev-environment": "^0.15.1",
56
56
  "@testing-library/jest-dom": "^6.2.0",
57
57
  "@types/js-beautify": "^1",
58
58
  "@types/node": "^20.11.5",