jupiter-dynamic-forms 1.1.0 → 1.2.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.
package/dist/index.mjs CHANGED
@@ -535,6 +535,7 @@ class XBRLFormBuilder {
535
535
  const sections = [];
536
536
  presentationData.roles.forEach((role) => {
537
537
  const section = this.buildSectionFromRole(role, periodStartDate, periodEndDate);
538
+ this.assignFieldColumnIds(section);
538
539
  sections.push(section);
539
540
  });
540
541
  return {
@@ -560,11 +561,13 @@ class XBRLFormBuilder {
560
561
  }
561
562
  });
562
563
  }
564
+ const columns = this.generateDefaultColumnsForRole(role, periodStartDate || "2025-01-01", periodEndDate || "2025-12-31");
563
565
  return {
564
566
  id: role.id,
565
567
  title,
566
568
  description: `Section for ${title}`,
567
569
  concepts: conceptTrees,
570
+ columns,
568
571
  expanded: false
569
572
  };
570
573
  }
@@ -607,11 +610,18 @@ class XBRLFormBuilder {
607
610
  static createFieldFromConcept(concept, periodStartDate, periodEndDate) {
608
611
  const fieldType = this.mapXBRLTypeToFieldType(concept.type);
609
612
  const label = this.getPreferredLabel(concept.labels);
613
+ let columnId = "default";
614
+ if (concept.periodType === "instant") {
615
+ columnId = "instant";
616
+ } else if (concept.periodType === "duration") {
617
+ columnId = "duration_start";
618
+ } else {
619
+ columnId = "default";
620
+ }
610
621
  const field = {
611
622
  id: `${concept.id}_field`,
612
623
  conceptId: concept.id,
613
- columnId: "default",
614
- // Default column, will be expanded later
624
+ columnId,
615
625
  type: fieldType,
616
626
  label,
617
627
  placeholder: `Enter ${label.toLowerCase()}`,
@@ -660,6 +670,162 @@ class XBRLFormBuilder {
660
670
  return terse.label;
661
671
  return labels[0].label;
662
672
  }
673
+ /**
674
+ * Assign appropriate column IDs to fields based on section's generated columns
675
+ */
676
+ static assignFieldColumnIds(section) {
677
+ if (!section.columns || section.columns.length === 0) {
678
+ return;
679
+ }
680
+ const columnIds = section.columns.map((col) => col.id);
681
+ section.concepts.forEach((conceptTree) => {
682
+ this.assignColumnIdsToConceptFields(conceptTree, columnIds);
683
+ });
684
+ }
685
+ /**
686
+ * Recursively assign column IDs to fields in a concept tree
687
+ */
688
+ static assignColumnIdsToConceptFields(conceptTree, columnIds) {
689
+ conceptTree.fields.forEach((field) => {
690
+ if (columnIds.includes("instant") && field.columnId === "instant") {
691
+ field.columnId = "instant";
692
+ } else if (columnIds.includes("duration_start") && field.columnId === "duration_start") {
693
+ field.columnId = "duration_start";
694
+ } else if (columnIds.includes("period_start") && (field.columnId === "duration_start" || field.columnId === "instant")) {
695
+ field.columnId = "period_start";
696
+ } else if (columnIds.length > 0) {
697
+ field.columnId = columnIds[0];
698
+ }
699
+ });
700
+ if (conceptTree.children) {
701
+ conceptTree.children.forEach((child) => {
702
+ this.assignColumnIdsToConceptFields(child, columnIds);
703
+ });
704
+ }
705
+ }
706
+ /**
707
+ * Get all non-abstract concepts from a role recursively
708
+ */
709
+ static getAllNonAbstractConcepts(role) {
710
+ var _a;
711
+ const nonAbstractConcepts = [];
712
+ if ((_a = role.presentationLinkbase) == null ? void 0 : _a.concepts) {
713
+ role.presentationLinkbase.concepts.forEach((concept) => {
714
+ this.collectNonAbstractConcepts(concept, nonAbstractConcepts);
715
+ });
716
+ }
717
+ return nonAbstractConcepts;
718
+ }
719
+ /**
720
+ * Recursively collect non-abstract concepts from a concept tree
721
+ */
722
+ static collectNonAbstractConcepts(concept, collection) {
723
+ if (!concept.elementAbstract) {
724
+ collection.push(concept);
725
+ }
726
+ if (concept.children && concept.children.length > 0) {
727
+ concept.children.forEach((child) => {
728
+ this.collectNonAbstractConcepts(child, collection);
729
+ });
730
+ }
731
+ }
732
+ /**
733
+ * Generate default columns based on period types of non-abstract concepts in a role
734
+ */
735
+ static generateDefaultColumnsForRole(role, periodStartDate, periodEndDate) {
736
+ const nonAbstractConcepts = this.getAllNonAbstractConcepts(role);
737
+ console.log(`📊 Analyzing role "${role.role}" with ${nonAbstractConcepts.length} non-abstract concepts`);
738
+ if (nonAbstractConcepts.length === 0) {
739
+ console.log("📊 No non-abstract concepts found, using default column");
740
+ return [{
741
+ id: "default",
742
+ title: "Value",
743
+ description: "Default value column",
744
+ type: "base",
745
+ order: 0,
746
+ removable: false
747
+ }];
748
+ }
749
+ const periodTypes = new Set(
750
+ nonAbstractConcepts.filter((concept) => concept.periodType).map((concept) => concept.periodType)
751
+ );
752
+ console.log(`📅 Found period types: ${Array.from(periodTypes).join(", ")}`);
753
+ const columns = [];
754
+ if (periodTypes.size === 0) {
755
+ console.log("📅 No period types found, using default column");
756
+ columns.push({
757
+ id: "default",
758
+ title: "Value",
759
+ description: "Default value column",
760
+ type: "base",
761
+ order: 0,
762
+ removable: false
763
+ });
764
+ } else if (periodTypes.size === 1 && periodTypes.has("instant")) {
765
+ console.log("📅 All concepts are instant type, creating single column");
766
+ columns.push({
767
+ id: "instant",
768
+ title: `As of ${this.formatDateForDisplay(periodStartDate)}`,
769
+ description: `Values as of ${periodStartDate}`,
770
+ type: "base",
771
+ order: 0,
772
+ removable: false
773
+ });
774
+ } else if (periodTypes.size === 1 && periodTypes.has("duration")) {
775
+ console.log("📅 All concepts are duration type, creating two columns");
776
+ columns.push({
777
+ id: "duration_start",
778
+ title: `From ${this.formatDateForDisplay(periodStartDate)}`,
779
+ description: `Period start: ${periodStartDate}`,
780
+ type: "base",
781
+ order: 0,
782
+ removable: false
783
+ });
784
+ columns.push({
785
+ id: "duration_end",
786
+ title: `To ${this.formatDateForDisplay(periodEndDate)}`,
787
+ description: `Period end: ${periodEndDate}`,
788
+ type: "base",
789
+ order: 1,
790
+ removable: false
791
+ });
792
+ } else {
793
+ console.log("📅 Mixed period types found, creating two columns for full range");
794
+ columns.push({
795
+ id: "period_start",
796
+ title: `From ${this.formatDateForDisplay(periodStartDate)}`,
797
+ description: `Period start: ${periodStartDate}`,
798
+ type: "base",
799
+ order: 0,
800
+ removable: false
801
+ });
802
+ columns.push({
803
+ id: "period_end",
804
+ title: `To ${this.formatDateForDisplay(periodEndDate)}`,
805
+ description: `Period end: ${periodEndDate}`,
806
+ type: "base",
807
+ order: 1,
808
+ removable: false
809
+ });
810
+ }
811
+ console.log(`📊 Generated ${columns.length} columns for role "${role.role}":`, columns.map((c2) => c2.title));
812
+ return columns;
813
+ }
814
+ /**
815
+ * Format date for display in column headers
816
+ */
817
+ static formatDateForDisplay(dateString) {
818
+ try {
819
+ const date = new Date(dateString);
820
+ return date.toLocaleDateString("en-US", {
821
+ year: "numeric",
822
+ month: "short",
823
+ day: "numeric"
824
+ });
825
+ } catch {
826
+ return dateString;
827
+ }
828
+ }
663
829
  /**
664
830
  * Extract entity name from entrypoint URL
665
831
  */
@@ -1847,7 +2013,7 @@ let JupiterDynamicForm = class extends LitElement {
1847
2013
  ${schema.sections.map((section) => html`
1848
2014
  <jupiter-form-section
1849
2015
  .section="${section}"
1850
- .columns="${this._columns}"
2016
+ .columns="${section.columns || this._columns}"
1851
2017
  .formData="${this._formData}"
1852
2018
  .disabled="${this.disabled || this.readonly}"
1853
2019
  .collapsible="${config.collapsibleSections !== false}"