jupiter-dynamic-forms 1.0.1 → 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
@@ -527,14 +527,15 @@ class XBRLFormBuilder {
527
527
  * Build form schema from XBRL input data
528
528
  * Creates accordion sections for each presentation role
529
529
  */
530
- static buildFormSchema(xbrlInput) {
530
+ static buildFormSchema(xbrlInput, periodStartDate, periodEndDate) {
531
531
  if (!xbrlInput.presentation || xbrlInput.presentation.length === 0) {
532
532
  throw new Error("XBRL presentation data is required");
533
533
  }
534
534
  const presentationData = xbrlInput.presentation[0];
535
535
  const sections = [];
536
536
  presentationData.roles.forEach((role) => {
537
- const section = this.buildSectionFromRole(role);
537
+ const section = this.buildSectionFromRole(role, periodStartDate, periodEndDate);
538
+ this.assignFieldColumnIds(section);
538
539
  sections.push(section);
539
540
  });
540
541
  return {
@@ -548,34 +549,36 @@ class XBRLFormBuilder {
548
549
  /**
549
550
  * Build a form section from a presentation role
550
551
  */
551
- static buildSectionFromRole(role) {
552
+ static buildSectionFromRole(role, periodStartDate, periodEndDate) {
552
553
  var _a;
553
554
  const title = this.extractRoleTitle(role.role);
554
555
  const conceptTrees = [];
555
556
  if ((_a = role.presentationLinkbase) == null ? void 0 : _a.concepts) {
556
557
  role.presentationLinkbase.concepts.forEach((concept) => {
557
- const conceptTree = this.buildConceptTree(concept, 0);
558
+ const conceptTree = this.buildConceptTree(concept, 0, periodStartDate, periodEndDate);
558
559
  if (conceptTree) {
559
560
  conceptTrees.push(conceptTree);
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
  }
571
574
  /**
572
575
  * Build concept tree from XBRL presentation concept
573
576
  */
574
- static buildConceptTree(concept, level) {
577
+ static buildConceptTree(concept, level, periodStartDate, periodEndDate) {
575
578
  const label = this.getPreferredLabel(concept.labels);
576
579
  const fields = [];
577
580
  if (!concept.elementAbstract) {
578
- const field = this.createFieldFromConcept(concept);
581
+ const field = this.createFieldFromConcept(concept, periodStartDate, periodEndDate);
579
582
  if (field) {
580
583
  fields.push(field);
581
584
  }
@@ -583,7 +586,7 @@ class XBRLFormBuilder {
583
586
  const children = [];
584
587
  if (concept.children && concept.children.length > 0) {
585
588
  concept.children.forEach((child) => {
586
- const childTree = this.buildConceptTree(child, level + 1);
589
+ const childTree = this.buildConceptTree(child, level + 1, periodStartDate, periodEndDate);
587
590
  if (childTree) {
588
591
  children.push(childTree);
589
592
  }
@@ -604,14 +607,21 @@ class XBRLFormBuilder {
604
607
  /**
605
608
  * Create form field from XBRL concept
606
609
  */
607
- static createFieldFromConcept(concept) {
610
+ static createFieldFromConcept(concept, periodStartDate, periodEndDate) {
608
611
  const fieldType = this.mapXBRLTypeToFieldType(concept.type);
609
612
  const label = this.getPreferredLabel(concept.labels);
610
- return {
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
+ }
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()}`,
@@ -620,6 +630,11 @@ class XBRLFormBuilder {
620
630
  disabled: concept.elementAbstract,
621
631
  defaultValue: null
622
632
  };
633
+ if (concept.periodType === "duration" || concept.periodType === "instant") {
634
+ field.periodStartDate = periodStartDate || "2025-01-01";
635
+ field.periodEndDate = periodEndDate || "2025-12-31";
636
+ }
637
+ return field;
623
638
  }
624
639
  /**
625
640
  * Map XBRL data type to form field type
@@ -655,6 +670,162 @@ class XBRLFormBuilder {
655
670
  return terse.label;
656
671
  return labels[0].label;
657
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
+ }
658
829
  /**
659
830
  * Extract entity name from entrypoint URL
660
831
  */
@@ -1568,6 +1739,8 @@ let JupiterDynamicForm = class extends LitElement {
1568
1739
  this.initialData = {};
1569
1740
  this.disabled = false;
1570
1741
  this.readonly = false;
1742
+ this.periodStartDate = "2025-01-01";
1743
+ this.periodEndDate = "2025-12-31";
1571
1744
  this._formData = {};
1572
1745
  this._columns = [];
1573
1746
  this._errors = [];
@@ -1590,7 +1763,12 @@ let JupiterDynamicForm = class extends LitElement {
1590
1763
  if (this.xbrlInput) {
1591
1764
  try {
1592
1765
  console.log("🔄 Initializing form from XBRL input:", this.xbrlInput);
1593
- this._currentSchema = XBRLFormBuilder.buildFormSchema(this.xbrlInput);
1766
+ console.log("📅 Using period dates:", this.periodStartDate, "to", this.periodEndDate);
1767
+ this._currentSchema = XBRLFormBuilder.buildFormSchema(
1768
+ this.xbrlInput,
1769
+ this.periodStartDate,
1770
+ this.periodEndDate
1771
+ );
1594
1772
  console.log("✅ Generated schema with sections:", this._currentSchema.sections.length);
1595
1773
  this._columns = [
1596
1774
  {
@@ -1835,7 +2013,7 @@ let JupiterDynamicForm = class extends LitElement {
1835
2013
  ${schema.sections.map((section) => html`
1836
2014
  <jupiter-form-section
1837
2015
  .section="${section}"
1838
- .columns="${this._columns}"
2016
+ .columns="${section.columns || this._columns}"
1839
2017
  .formData="${this._formData}"
1840
2018
  .disabled="${this.disabled || this.readonly}"
1841
2019
  .collapsible="${config.collapsibleSections !== false}"
@@ -2044,6 +2222,12 @@ __decorateClass([
2044
2222
  __decorateClass([
2045
2223
  n2({ type: Boolean })
2046
2224
  ], JupiterDynamicForm.prototype, "readonly", 2);
2225
+ __decorateClass([
2226
+ n2({ type: String })
2227
+ ], JupiterDynamicForm.prototype, "periodStartDate", 2);
2228
+ __decorateClass([
2229
+ n2({ type: String })
2230
+ ], JupiterDynamicForm.prototype, "periodEndDate", 2);
2047
2231
  __decorateClass([
2048
2232
  r()
2049
2233
  ], JupiterDynamicForm.prototype, "_formData", 2);