ninegrid2 6.912.0 → 6.913.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.
@@ -121646,31 +121646,160 @@ class nxForm extends HTMLElement {
121646
121646
  customElements.define("nx-form", nxForm);
121647
121647
 
121648
121648
  class nxTitle extends HTMLElement {
121649
+
121650
+ #breadcrumbPath = []; // Breadcrumb 경로를 저장할 내부 상태 초기화
121651
+
121649
121652
  constructor() {
121650
121653
  super();
121651
121654
  this.attachShadow({ mode: "open" });
121652
121655
  }
121653
121656
 
121654
121657
  connectedCallback() {
121655
- this.#randerer();
121658
+ this.#renderer(); // 초기 렌더링 (빵 부스러기 데이터는 아직 없음)
121659
+ this.#generateBreadcrumb(); // 빵 부스러기 데이터 생성 및 최종 렌더링 트리거
121656
121660
  }
121657
121661
 
121658
- #randerer = () => {
121662
+ // Breadcrumb 경로를 구성하는 비공개 메서드
121663
+ #generateBreadcrumb = () => {
121664
+ const path = ["Home"]; // 기본 경로 시작은 Home
121665
+
121666
+ // 현재 nx-title의 캡션을 현재 페이지 캡션으로 사용
121667
+ const currentPageCaption = this.getAttribute("caption") || "Current Page";
121668
+
121669
+ // 'active' 클래스를 가진 가장 하위 nx-side-menu-item 찾기
121670
+ const activeMenuItem = document.querySelector('nx-side-menu-item.active');
121671
+
121672
+ if (activeMenuItem) {
121673
+ const tempPath = []; // 임시 경로를 저장하여 역순으로 처리
121674
+
121675
+ // 1. 현재 활성화된 메뉴 항목의 캡션을 추가 (예: '심의자료')
121676
+ const currentActiveCaption = activeMenuItem.getAttribute('caption');
121677
+ if (currentActiveCaption) {
121678
+ tempPath.unshift(currentActiveCaption); // 맨 앞에 추가
121679
+ }
121680
+
121681
+ // 2. 현재 activeMenuItem의 이전 형제들을 탐색하여 'group'이 아닌 상위 메뉴(예: '심의자료')를 찾고,
121682
+ // 그 이전에 있는 'group' 메뉴(예: '자료관리')를 찾습니다.
121683
+ let currentElement = activeMenuItem;
121684
+ let foundGroupParent = false; // '자료관리' 그룹을 찾았는지 여부
121685
+
121686
+ // activeMenuItem부터 이전 형제들을 역으로 탐색
121687
+ while (currentElement) {
121688
+ // 현재 activeMenuItem이 'group' 속성이 없는 일반 메뉴일 경우,
121689
+ // 바로 그 자체가 두 번째 레벨 메뉴(예: '심의자료')의 역할을 합니다.
121690
+ // 이미 activeMenuItem의 캡션은 추가되었으므로 여기서는 건너뜁니다.
121691
+
121692
+ // 이전 형제 탐색
121693
+ currentElement = currentElement.previousElementSibling;
121694
+
121695
+ if (currentElement && currentElement.tagName === 'NX-SIDE-MENU-ITEM') {
121696
+ const caption = currentElement.getAttribute('caption');
121697
+
121698
+ // 'group' 속성을 가진 메뉴 항목 찾기 (예: '자료관리')
121699
+ if (currentElement.hasAttribute('group')) {
121700
+ if (caption && !foundGroupParent) { // 이미 찾지 않은 경우에만 추가
121701
+ tempPath.unshift(caption);
121702
+ foundGroupParent = true;
121703
+ }
121704
+ // 그룹을 찾으면 탐색 중단 (가장 가까운 상위 그룹만 필요)
121705
+ break;
121706
+ }
121707
+ }
121708
+ }
121709
+
121710
+ // tempPath의 내용을 Home 다음으로 삽입
121711
+ // activeMenuItem이 '심의자료' 역할을 하고, 그 이전에 '자료관리' 그룹이 있다면
121712
+ // tempPath: ['자료관리', '심의자료']
121713
+ path.push(...tempPath);
121714
+ }
121715
+
121716
+ // 마지막으로 현재 페이지의 구체적인 제목 추가
121717
+ path.push(currentPageCaption);
121718
+
121719
+ this.#breadcrumbPath = path;
121720
+ this.#renderer();
121721
+ };
121722
+
121723
+ #renderer = () => {
121659
121724
  const caption = this.getAttribute("caption") || "No Caption";
121660
121725
 
121726
+ // 내부 HTML (Light DOM)은 이 컴포넌트의 자식으로 넘어오는 콘텐츠이므로,
121727
+ // Breadcrumb에서는 사용하지 않는다면 제거하는 것이 맞습니다.
121728
+ // 여기서는 이전에 contents 변수에 저장했지만 사용하지 않았으므로, 제거 로직을 유지합니다.
121661
121729
  this.innerHTML.trim();
121662
121730
  this.innerHTML = ""; // 기존 내부 HTML 제거
121663
121731
 
121732
+ // Shadow DOM 초기화 (새로운 콘텐츠를 위해)
121733
+ this.shadowRoot.innerHTML = "";
121734
+
121664
121735
  const htmlTmpl = document.createElement("template");
121665
121736
  htmlTmpl.innerHTML = `
121666
- <style>
121667
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTitle.css";
121668
- ${ninegrid.getCustomPath(this,"nxTitle.css")}
121669
- </style>
121737
+ <style>
121738
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTitle.css";
121739
+ ${ninegrid.getCustomPath(this,"nxTitle.css")}
121740
+
121741
+ /* Breadcrumb CSS */
121742
+ .breadcrumb-container {
121743
+ padding: 10px 15px;
121744
+ background-color: #f5f5f5;
121745
+ border-bottom: 1px solid #eee;
121746
+ font-size: 13px;
121747
+ color: #777;
121748
+ display: flex; /* 가로 정렬 */
121749
+ align-items: center;
121750
+ justify-content: flex-end; /* 오른쪽 정렬 */
121751
+ margin-bottom: 15px; /* 제목과의 간격 */
121752
+ }
121753
+ .breadcrumb-item {
121754
+ white-space: nowrap; /* 줄바꿈 방지 */
121755
+ }
121756
+ .breadcrumb-separator {
121757
+ margin: 0 5px;
121758
+ color: #bbb;
121759
+ }
121760
+ .breadcrumb-current {
121761
+ font-weight: bold;
121762
+ color: #333;
121763
+ }
121764
+
121765
+ /* Title CSS (아이콘 및 텍스트 스타일) */
121766
+ .title-wrapper {
121767
+ display: flex;
121768
+ align-items: center;
121769
+ padding: 10px 15px;
121770
+ border-bottom: 2px solid #eee;
121771
+ margin-bottom: 15px;
121772
+ }
121773
+ .title-icon {
121774
+ margin-right: 10px;
121775
+ font-size: 20px;
121776
+ color: #333;
121777
+ /* Font Awesome 등 외부 아이콘 라이브러리를 사용하는 경우 */
121778
+ /* font-family: 'Font Awesome 5 Free'; */
121779
+ /* font-weight: 900; */
121780
+ /* content: '\f054'; */
121781
+ }
121782
+ .title-text {
121783
+ font-size: 18px;
121784
+ font-weight: bold;
121785
+ color: #333;
121786
+ }
121787
+ </style>
121670
121788
 
121671
121789
  <div class="wrapper">
121672
- <i class="title-icon"></i> <span class="title-text">${caption}</span>
121673
- </div>
121790
+ <div class="breadcrumb-container">
121791
+ ${this.#breadcrumbPath.map((item, index) => `
121792
+ <span class="breadcrumb-item ${index === this.#breadcrumbPath.length - 1 ? 'breadcrumb-current' : ''}">
121793
+ ${item}
121794
+ </span>
121795
+ ${index < this.#breadcrumbPath.length - 1 ? '<span class="breadcrumb-separator"> &gt; </span>' : ''}
121796
+ `).join('')}
121797
+ </div>
121798
+ <div class="title-wrapper">
121799
+ <i class="title-icon"></i>
121800
+ <span class="title-text">${caption}</span>
121801
+ </div>
121802
+ </div>
121674
121803
  `;
121675
121804
 
121676
121805
  this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
@@ -121642,31 +121642,160 @@ class nxForm extends HTMLElement {
121642
121642
  customElements.define("nx-form", nxForm);
121643
121643
 
121644
121644
  class nxTitle extends HTMLElement {
121645
+
121646
+ #breadcrumbPath = []; // Breadcrumb 경로를 저장할 내부 상태 초기화
121647
+
121645
121648
  constructor() {
121646
121649
  super();
121647
121650
  this.attachShadow({ mode: "open" });
121648
121651
  }
121649
121652
 
121650
121653
  connectedCallback() {
121651
- this.#randerer();
121654
+ this.#renderer(); // 초기 렌더링 (빵 부스러기 데이터는 아직 없음)
121655
+ this.#generateBreadcrumb(); // 빵 부스러기 데이터 생성 및 최종 렌더링 트리거
121652
121656
  }
121653
121657
 
121654
- #randerer = () => {
121658
+ // Breadcrumb 경로를 구성하는 비공개 메서드
121659
+ #generateBreadcrumb = () => {
121660
+ const path = ["Home"]; // 기본 경로 시작은 Home
121661
+
121662
+ // 현재 nx-title의 캡션을 현재 페이지 캡션으로 사용
121663
+ const currentPageCaption = this.getAttribute("caption") || "Current Page";
121664
+
121665
+ // 'active' 클래스를 가진 가장 하위 nx-side-menu-item 찾기
121666
+ const activeMenuItem = document.querySelector('nx-side-menu-item.active');
121667
+
121668
+ if (activeMenuItem) {
121669
+ const tempPath = []; // 임시 경로를 저장하여 역순으로 처리
121670
+
121671
+ // 1. 현재 활성화된 메뉴 항목의 캡션을 추가 (예: '심의자료')
121672
+ const currentActiveCaption = activeMenuItem.getAttribute('caption');
121673
+ if (currentActiveCaption) {
121674
+ tempPath.unshift(currentActiveCaption); // 맨 앞에 추가
121675
+ }
121676
+
121677
+ // 2. 현재 activeMenuItem의 이전 형제들을 탐색하여 'group'이 아닌 상위 메뉴(예: '심의자료')를 찾고,
121678
+ // 그 이전에 있는 'group' 메뉴(예: '자료관리')를 찾습니다.
121679
+ let currentElement = activeMenuItem;
121680
+ let foundGroupParent = false; // '자료관리' 그룹을 찾았는지 여부
121681
+
121682
+ // activeMenuItem부터 이전 형제들을 역으로 탐색
121683
+ while (currentElement) {
121684
+ // 현재 activeMenuItem이 'group' 속성이 없는 일반 메뉴일 경우,
121685
+ // 바로 그 자체가 두 번째 레벨 메뉴(예: '심의자료')의 역할을 합니다.
121686
+ // 이미 activeMenuItem의 캡션은 추가되었으므로 여기서는 건너뜁니다.
121687
+
121688
+ // 이전 형제 탐색
121689
+ currentElement = currentElement.previousElementSibling;
121690
+
121691
+ if (currentElement && currentElement.tagName === 'NX-SIDE-MENU-ITEM') {
121692
+ const caption = currentElement.getAttribute('caption');
121693
+
121694
+ // 'group' 속성을 가진 메뉴 항목 찾기 (예: '자료관리')
121695
+ if (currentElement.hasAttribute('group')) {
121696
+ if (caption && !foundGroupParent) { // 이미 찾지 않은 경우에만 추가
121697
+ tempPath.unshift(caption);
121698
+ foundGroupParent = true;
121699
+ }
121700
+ // 그룹을 찾으면 탐색 중단 (가장 가까운 상위 그룹만 필요)
121701
+ break;
121702
+ }
121703
+ }
121704
+ }
121705
+
121706
+ // tempPath의 내용을 Home 다음으로 삽입
121707
+ // activeMenuItem이 '심의자료' 역할을 하고, 그 이전에 '자료관리' 그룹이 있다면
121708
+ // tempPath: ['자료관리', '심의자료']
121709
+ path.push(...tempPath);
121710
+ }
121711
+
121712
+ // 마지막으로 현재 페이지의 구체적인 제목 추가
121713
+ path.push(currentPageCaption);
121714
+
121715
+ this.#breadcrumbPath = path;
121716
+ this.#renderer();
121717
+ };
121718
+
121719
+ #renderer = () => {
121655
121720
  const caption = this.getAttribute("caption") || "No Caption";
121656
121721
 
121722
+ // 내부 HTML (Light DOM)은 이 컴포넌트의 자식으로 넘어오는 콘텐츠이므로,
121723
+ // Breadcrumb에서는 사용하지 않는다면 제거하는 것이 맞습니다.
121724
+ // 여기서는 이전에 contents 변수에 저장했지만 사용하지 않았으므로, 제거 로직을 유지합니다.
121657
121725
  this.innerHTML.trim();
121658
121726
  this.innerHTML = ""; // 기존 내부 HTML 제거
121659
121727
 
121728
+ // Shadow DOM 초기화 (새로운 콘텐츠를 위해)
121729
+ this.shadowRoot.innerHTML = "";
121730
+
121660
121731
  const htmlTmpl = document.createElement("template");
121661
121732
  htmlTmpl.innerHTML = `
121662
- <style>
121663
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTitle.css";
121664
- ${ninegrid.getCustomPath(this,"nxTitle.css")}
121665
- </style>
121733
+ <style>
121734
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTitle.css";
121735
+ ${ninegrid.getCustomPath(this,"nxTitle.css")}
121736
+
121737
+ /* Breadcrumb CSS */
121738
+ .breadcrumb-container {
121739
+ padding: 10px 15px;
121740
+ background-color: #f5f5f5;
121741
+ border-bottom: 1px solid #eee;
121742
+ font-size: 13px;
121743
+ color: #777;
121744
+ display: flex; /* 가로 정렬 */
121745
+ align-items: center;
121746
+ justify-content: flex-end; /* 오른쪽 정렬 */
121747
+ margin-bottom: 15px; /* 제목과의 간격 */
121748
+ }
121749
+ .breadcrumb-item {
121750
+ white-space: nowrap; /* 줄바꿈 방지 */
121751
+ }
121752
+ .breadcrumb-separator {
121753
+ margin: 0 5px;
121754
+ color: #bbb;
121755
+ }
121756
+ .breadcrumb-current {
121757
+ font-weight: bold;
121758
+ color: #333;
121759
+ }
121760
+
121761
+ /* Title CSS (아이콘 및 텍스트 스타일) */
121762
+ .title-wrapper {
121763
+ display: flex;
121764
+ align-items: center;
121765
+ padding: 10px 15px;
121766
+ border-bottom: 2px solid #eee;
121767
+ margin-bottom: 15px;
121768
+ }
121769
+ .title-icon {
121770
+ margin-right: 10px;
121771
+ font-size: 20px;
121772
+ color: #333;
121773
+ /* Font Awesome 등 외부 아이콘 라이브러리를 사용하는 경우 */
121774
+ /* font-family: 'Font Awesome 5 Free'; */
121775
+ /* font-weight: 900; */
121776
+ /* content: '\f054'; */
121777
+ }
121778
+ .title-text {
121779
+ font-size: 18px;
121780
+ font-weight: bold;
121781
+ color: #333;
121782
+ }
121783
+ </style>
121666
121784
 
121667
121785
  <div class="wrapper">
121668
- <i class="title-icon"></i> <span class="title-text">${caption}</span>
121669
- </div>
121786
+ <div class="breadcrumb-container">
121787
+ ${this.#breadcrumbPath.map((item, index) => `
121788
+ <span class="breadcrumb-item ${index === this.#breadcrumbPath.length - 1 ? 'breadcrumb-current' : ''}">
121789
+ ${item}
121790
+ </span>
121791
+ ${index < this.#breadcrumbPath.length - 1 ? '<span class="breadcrumb-separator"> &gt; </span>' : ''}
121792
+ `).join('')}
121793
+ </div>
121794
+ <div class="title-wrapper">
121795
+ <i class="title-icon"></i>
121796
+ <span class="title-text">${caption}</span>
121797
+ </div>
121798
+ </div>
121670
121799
  `;
121671
121800
 
121672
121801
  this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
@@ -1,35 +1,180 @@
1
1
  import ninegrid from "../index.js";
2
2
 
3
3
  class nxTitle extends HTMLElement {
4
+
5
+ #breadcrumbPath = []; // Breadcrumb 경로를 저장할 내부 상태 초기화
6
+
4
7
  constructor() {
5
8
  super();
6
9
  this.attachShadow({ mode: "open" });
7
10
  }
8
11
 
9
12
  connectedCallback() {
10
- this.#randerer();
13
+ this.#renderer(); // 초기 렌더링 (빵 부스러기 데이터는 아직 없음)
14
+ this.#generateBreadcrumb(); // 빵 부스러기 데이터 생성 및 최종 렌더링 트리거
11
15
  }
12
16
 
13
- #randerer = () => {
17
+ // Breadcrumb 경로를 구성하는 비공개 메서드
18
+ #generateBreadcrumb = () => {
19
+ const path = ["Home"]; // 기본 경로 시작은 Home
20
+
21
+ // 현재 nx-title의 캡션을 현재 페이지 캡션으로 사용
22
+ const currentPageCaption = this.getAttribute("caption") || "Current Page";
23
+
24
+ // 'active' 클래스를 가진 가장 하위 nx-side-menu-item 찾기
25
+ const activeMenuItem = document.querySelector('nx-side-menu-item.active');
26
+
27
+ if (activeMenuItem) {
28
+ const tempPath = []; // 임시 경로를 저장하여 역순으로 처리
29
+
30
+ // 1. 현재 활성화된 메뉴 항목의 캡션을 추가 (예: '심의자료')
31
+ const currentActiveCaption = activeMenuItem.getAttribute('caption');
32
+ if (currentActiveCaption) {
33
+ tempPath.unshift(currentActiveCaption); // 맨 앞에 추가
34
+ }
35
+
36
+ // 2. 현재 activeMenuItem의 이전 형제들을 탐색하여 'group'이 아닌 상위 메뉴(예: '심의자료')를 찾고,
37
+ // 그 이전에 있는 'group' 메뉴(예: '자료관리')를 찾습니다.
38
+ let currentElement = activeMenuItem;
39
+ let foundNonGroupParent = false; // '자료관리'와 '심의자료' 사이의 메뉴를 찾았는지 여부
40
+ let foundGroupParent = false; // '자료관리' 그룹을 찾았는지 여부
41
+
42
+ // activeMenuItem부터 이전 형제들을 역으로 탐색
43
+ while (currentElement) {
44
+ // 현재 activeMenuItem이 'group' 속성이 없는 일반 메뉴일 경우,
45
+ // 바로 그 자체가 두 번째 레벨 메뉴(예: '심의자료')의 역할을 합니다.
46
+ // 이미 activeMenuItem의 캡션은 추가되었으므로 여기서는 건너뜁니다.
47
+
48
+ // 이전 형제 탐색
49
+ currentElement = currentElement.previousElementSibling;
50
+
51
+ if (currentElement && currentElement.tagName === 'NX-SIDE-MENU-ITEM') {
52
+ const caption = currentElement.getAttribute('caption');
53
+
54
+ // 'group' 속성을 가진 메뉴 항목 찾기 (예: '자료관리')
55
+ if (currentElement.hasAttribute('group')) {
56
+ if (caption && !foundGroupParent) { // 이미 찾지 않은 경우에만 추가
57
+ tempPath.unshift(caption);
58
+ foundGroupParent = true;
59
+ }
60
+ // 그룹을 찾으면 탐색 중단 (가장 가까운 상위 그룹만 필요)
61
+ break;
62
+ }
63
+ // 'group' 속성이 없고, 아직 '심의자료' 같은 중간 부모를 찾지 않았다면 추가
64
+ // (activeMenuItem이 이미 최하위 메뉴 캡션을 가지고 있으므로,
65
+ // 이 로직은 activeMenuItem이 아닌 그 위의 일반 메뉴를 위한 것임)
66
+ else if (caption && !foundNonGroupParent) {
67
+ // 이 부분은 currentActiveCaption이 '심의자료 상세'이고,
68
+ // activeMenuItem 자체가 '심의자료'일 때 문제가 될 수 있으니 주의
69
+ // 만약 activeMenuItem이 항상 최하위 캡션을 가진다면,
70
+ // 이 nonGroupParent 로직은 activeMenuItem의 직접적인 부모 (그룹이 아닌)를 찾는 데 쓰여야 함.
71
+ // 현재 시나리오에서는 'activeMenuItem'이 '심의자료' 역할을 할 가능성이 높음.
72
+ // '심의자료 상세'는 currentPageCaption이므로.
73
+
74
+ // activeMenuItem이 '심의자료' 역할을 한다고 가정하고,
75
+ // 이 루프에서는 그 '심의자료' 위의 '자료관리' 그룹만 찾도록 수정합니다.
76
+ // 즉, previousElementSibling은 현재 active 메뉴의 "옆에 있는" 그룹을 찾아야 합니다.
77
+ }
78
+ }
79
+ }
80
+
81
+ // tempPath의 내용을 Home 다음으로 삽입
82
+ // activeMenuItem이 '심의자료' 역할을 하고, 그 이전에 '자료관리' 그룹이 있다면
83
+ // tempPath: ['자료관리', '심의자료']
84
+ path.push(...tempPath);
85
+ }
86
+
87
+ // 마지막으로 현재 페이지의 구체적인 제목 추가
88
+ path.push(currentPageCaption);
89
+
90
+ this.#breadcrumbPath = path;
91
+ this.#renderer();
92
+ };
93
+
94
+ #renderer = () => {
14
95
  const caption = this.getAttribute("caption") || "No Caption";
15
96
 
97
+ // 내부 HTML (Light DOM)은 이 컴포넌트의 자식으로 넘어오는 콘텐츠이므로,
98
+ // Breadcrumb에서는 사용하지 않는다면 제거하는 것이 맞습니다.
99
+ // 여기서는 이전에 contents 변수에 저장했지만 사용하지 않았으므로, 제거 로직을 유지합니다.
16
100
  const contents = this.innerHTML.trim();
17
101
  this.innerHTML = ""; // 기존 내부 HTML 제거
18
102
 
103
+ // Shadow DOM 초기화 (새로운 콘텐츠를 위해)
104
+ this.shadowRoot.innerHTML = "";
105
+
19
106
  const htmlTmpl = document.createElement("template");
20
107
  htmlTmpl.innerHTML = `
21
- <style>
22
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTitle.css";
23
- ${ninegrid.getCustomPath(this,"nxTitle.css")}
24
- </style>
108
+ <style>
109
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTitle.css";
110
+ ${ninegrid.getCustomPath(this,"nxTitle.css")}
111
+
112
+ /* Breadcrumb CSS */
113
+ .breadcrumb-container {
114
+ padding: 10px 15px;
115
+ background-color: #f5f5f5;
116
+ border-bottom: 1px solid #eee;
117
+ font-size: 13px;
118
+ color: #777;
119
+ display: flex; /* 가로 정렬 */
120
+ align-items: center;
121
+ justify-content: flex-end; /* 오른쪽 정렬 */
122
+ margin-bottom: 15px; /* 제목과의 간격 */
123
+ }
124
+ .breadcrumb-item {
125
+ white-space: nowrap; /* 줄바꿈 방지 */
126
+ }
127
+ .breadcrumb-separator {
128
+ margin: 0 5px;
129
+ color: #bbb;
130
+ }
131
+ .breadcrumb-current {
132
+ font-weight: bold;
133
+ color: #333;
134
+ }
135
+
136
+ /* Title CSS (아이콘 및 텍스트 스타일) */
137
+ .title-wrapper {
138
+ display: flex;
139
+ align-items: center;
140
+ padding: 10px 15px;
141
+ border-bottom: 2px solid #eee;
142
+ margin-bottom: 15px;
143
+ }
144
+ .title-icon {
145
+ margin-right: 10px;
146
+ font-size: 20px;
147
+ color: #333;
148
+ /* Font Awesome 등 외부 아이콘 라이브러리를 사용하는 경우 */
149
+ /* font-family: 'Font Awesome 5 Free'; */
150
+ /* font-weight: 900; */
151
+ /* content: '\f054'; */
152
+ }
153
+ .title-text {
154
+ font-size: 18px;
155
+ font-weight: bold;
156
+ color: #333;
157
+ }
158
+ </style>
25
159
 
26
160
  <div class="wrapper">
27
- <i class="title-icon"></i> <span class="title-text">${caption}</span>
28
- </div>
161
+ <div class="breadcrumb-container">
162
+ ${this.#breadcrumbPath.map((item, index) => `
163
+ <span class="breadcrumb-item ${index === this.#breadcrumbPath.length - 1 ? 'breadcrumb-current' : ''}">
164
+ ${item}
165
+ </span>
166
+ ${index < this.#breadcrumbPath.length - 1 ? '<span class="breadcrumb-separator"> &gt; </span>' : ''}
167
+ `).join('')}
168
+ </div>
169
+ <div class="title-wrapper">
170
+ <i class="title-icon"></i>
171
+ <span class="title-text">${caption}</span>
172
+ </div>
173
+ </div>
29
174
  `;
30
175
 
31
176
  this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
32
177
  }
33
178
  }
34
179
 
35
- customElements.define("nx-title", nxTitle);
180
+ customElements.define("nx-title", nxTitle);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ninegrid2",
3
3
  "type": "module",
4
- "version": "6.912.0",
4
+ "version": "6.913.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
package/src/nx/nxTitle.js CHANGED
@@ -1,35 +1,180 @@
1
1
  import ninegrid from "../index.js";
2
2
 
3
3
  class nxTitle extends HTMLElement {
4
+
5
+ #breadcrumbPath = []; // Breadcrumb 경로를 저장할 내부 상태 초기화
6
+
4
7
  constructor() {
5
8
  super();
6
9
  this.attachShadow({ mode: "open" });
7
10
  }
8
11
 
9
12
  connectedCallback() {
10
- this.#randerer();
13
+ this.#renderer(); // 초기 렌더링 (빵 부스러기 데이터는 아직 없음)
14
+ this.#generateBreadcrumb(); // 빵 부스러기 데이터 생성 및 최종 렌더링 트리거
11
15
  }
12
16
 
13
- #randerer = () => {
17
+ // Breadcrumb 경로를 구성하는 비공개 메서드
18
+ #generateBreadcrumb = () => {
19
+ const path = ["Home"]; // 기본 경로 시작은 Home
20
+
21
+ // 현재 nx-title의 캡션을 현재 페이지 캡션으로 사용
22
+ const currentPageCaption = this.getAttribute("caption") || "Current Page";
23
+
24
+ // 'active' 클래스를 가진 가장 하위 nx-side-menu-item 찾기
25
+ const activeMenuItem = document.querySelector('nx-side-menu-item.active');
26
+
27
+ if (activeMenuItem) {
28
+ const tempPath = []; // 임시 경로를 저장하여 역순으로 처리
29
+
30
+ // 1. 현재 활성화된 메뉴 항목의 캡션을 추가 (예: '심의자료')
31
+ const currentActiveCaption = activeMenuItem.getAttribute('caption');
32
+ if (currentActiveCaption) {
33
+ tempPath.unshift(currentActiveCaption); // 맨 앞에 추가
34
+ }
35
+
36
+ // 2. 현재 activeMenuItem의 이전 형제들을 탐색하여 'group'이 아닌 상위 메뉴(예: '심의자료')를 찾고,
37
+ // 그 이전에 있는 'group' 메뉴(예: '자료관리')를 찾습니다.
38
+ let currentElement = activeMenuItem;
39
+ let foundNonGroupParent = false; // '자료관리'와 '심의자료' 사이의 메뉴를 찾았는지 여부
40
+ let foundGroupParent = false; // '자료관리' 그룹을 찾았는지 여부
41
+
42
+ // activeMenuItem부터 이전 형제들을 역으로 탐색
43
+ while (currentElement) {
44
+ // 현재 activeMenuItem이 'group' 속성이 없는 일반 메뉴일 경우,
45
+ // 바로 그 자체가 두 번째 레벨 메뉴(예: '심의자료')의 역할을 합니다.
46
+ // 이미 activeMenuItem의 캡션은 추가되었으므로 여기서는 건너뜁니다.
47
+
48
+ // 이전 형제 탐색
49
+ currentElement = currentElement.previousElementSibling;
50
+
51
+ if (currentElement && currentElement.tagName === 'NX-SIDE-MENU-ITEM') {
52
+ const caption = currentElement.getAttribute('caption');
53
+
54
+ // 'group' 속성을 가진 메뉴 항목 찾기 (예: '자료관리')
55
+ if (currentElement.hasAttribute('group')) {
56
+ if (caption && !foundGroupParent) { // 이미 찾지 않은 경우에만 추가
57
+ tempPath.unshift(caption);
58
+ foundGroupParent = true;
59
+ }
60
+ // 그룹을 찾으면 탐색 중단 (가장 가까운 상위 그룹만 필요)
61
+ break;
62
+ }
63
+ // 'group' 속성이 없고, 아직 '심의자료' 같은 중간 부모를 찾지 않았다면 추가
64
+ // (activeMenuItem이 이미 최하위 메뉴 캡션을 가지고 있으므로,
65
+ // 이 로직은 activeMenuItem이 아닌 그 위의 일반 메뉴를 위한 것임)
66
+ else if (caption && !foundNonGroupParent) {
67
+ // 이 부분은 currentActiveCaption이 '심의자료 상세'이고,
68
+ // activeMenuItem 자체가 '심의자료'일 때 문제가 될 수 있으니 주의
69
+ // 만약 activeMenuItem이 항상 최하위 캡션을 가진다면,
70
+ // 이 nonGroupParent 로직은 activeMenuItem의 직접적인 부모 (그룹이 아닌)를 찾는 데 쓰여야 함.
71
+ // 현재 시나리오에서는 'activeMenuItem'이 '심의자료' 역할을 할 가능성이 높음.
72
+ // '심의자료 상세'는 currentPageCaption이므로.
73
+
74
+ // activeMenuItem이 '심의자료' 역할을 한다고 가정하고,
75
+ // 이 루프에서는 그 '심의자료' 위의 '자료관리' 그룹만 찾도록 수정합니다.
76
+ // 즉, previousElementSibling은 현재 active 메뉴의 "옆에 있는" 그룹을 찾아야 합니다.
77
+ }
78
+ }
79
+ }
80
+
81
+ // tempPath의 내용을 Home 다음으로 삽입
82
+ // activeMenuItem이 '심의자료' 역할을 하고, 그 이전에 '자료관리' 그룹이 있다면
83
+ // tempPath: ['자료관리', '심의자료']
84
+ path.push(...tempPath);
85
+ }
86
+
87
+ // 마지막으로 현재 페이지의 구체적인 제목 추가
88
+ path.push(currentPageCaption);
89
+
90
+ this.#breadcrumbPath = path;
91
+ this.#renderer();
92
+ };
93
+
94
+ #renderer = () => {
14
95
  const caption = this.getAttribute("caption") || "No Caption";
15
96
 
97
+ // 내부 HTML (Light DOM)은 이 컴포넌트의 자식으로 넘어오는 콘텐츠이므로,
98
+ // Breadcrumb에서는 사용하지 않는다면 제거하는 것이 맞습니다.
99
+ // 여기서는 이전에 contents 변수에 저장했지만 사용하지 않았으므로, 제거 로직을 유지합니다.
16
100
  const contents = this.innerHTML.trim();
17
101
  this.innerHTML = ""; // 기존 내부 HTML 제거
18
102
 
103
+ // Shadow DOM 초기화 (새로운 콘텐츠를 위해)
104
+ this.shadowRoot.innerHTML = "";
105
+
19
106
  const htmlTmpl = document.createElement("template");
20
107
  htmlTmpl.innerHTML = `
21
- <style>
22
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTitle.css";
23
- ${ninegrid.getCustomPath(this,"nxTitle.css")}
24
- </style>
108
+ <style>
109
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTitle.css";
110
+ ${ninegrid.getCustomPath(this,"nxTitle.css")}
111
+
112
+ /* Breadcrumb CSS */
113
+ .breadcrumb-container {
114
+ padding: 10px 15px;
115
+ background-color: #f5f5f5;
116
+ border-bottom: 1px solid #eee;
117
+ font-size: 13px;
118
+ color: #777;
119
+ display: flex; /* 가로 정렬 */
120
+ align-items: center;
121
+ justify-content: flex-end; /* 오른쪽 정렬 */
122
+ margin-bottom: 15px; /* 제목과의 간격 */
123
+ }
124
+ .breadcrumb-item {
125
+ white-space: nowrap; /* 줄바꿈 방지 */
126
+ }
127
+ .breadcrumb-separator {
128
+ margin: 0 5px;
129
+ color: #bbb;
130
+ }
131
+ .breadcrumb-current {
132
+ font-weight: bold;
133
+ color: #333;
134
+ }
135
+
136
+ /* Title CSS (아이콘 및 텍스트 스타일) */
137
+ .title-wrapper {
138
+ display: flex;
139
+ align-items: center;
140
+ padding: 10px 15px;
141
+ border-bottom: 2px solid #eee;
142
+ margin-bottom: 15px;
143
+ }
144
+ .title-icon {
145
+ margin-right: 10px;
146
+ font-size: 20px;
147
+ color: #333;
148
+ /* Font Awesome 등 외부 아이콘 라이브러리를 사용하는 경우 */
149
+ /* font-family: 'Font Awesome 5 Free'; */
150
+ /* font-weight: 900; */
151
+ /* content: '\f054'; */
152
+ }
153
+ .title-text {
154
+ font-size: 18px;
155
+ font-weight: bold;
156
+ color: #333;
157
+ }
158
+ </style>
25
159
 
26
160
  <div class="wrapper">
27
- <i class="title-icon"></i> <span class="title-text">${caption}</span>
28
- </div>
161
+ <div class="breadcrumb-container">
162
+ ${this.#breadcrumbPath.map((item, index) => `
163
+ <span class="breadcrumb-item ${index === this.#breadcrumbPath.length - 1 ? 'breadcrumb-current' : ''}">
164
+ ${item}
165
+ </span>
166
+ ${index < this.#breadcrumbPath.length - 1 ? '<span class="breadcrumb-separator"> &gt; </span>' : ''}
167
+ `).join('')}
168
+ </div>
169
+ <div class="title-wrapper">
170
+ <i class="title-icon"></i>
171
+ <span class="title-text">${caption}</span>
172
+ </div>
173
+ </div>
29
174
  `;
30
175
 
31
176
  this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
32
177
  }
33
178
  }
34
179
 
35
- customElements.define("nx-title", nxTitle);
180
+ customElements.define("nx-title", nxTitle);