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.
- package/dist/bundle.cjs.js +137 -8
- package/dist/bundle.esm.js +137 -8
- package/dist/nx/nxTitle.js +154 -9
- package/package.json +1 -1
- package/src/nx/nxTitle.js +154 -9
package/dist/bundle.cjs.js
CHANGED
|
@@ -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.#
|
|
121658
|
+
this.#renderer(); // 초기 렌더링 (빵 부스러기 데이터는 아직 없음)
|
|
121659
|
+
this.#generateBreadcrumb(); // 빵 부스러기 데이터 생성 및 최종 렌더링 트리거
|
|
121656
121660
|
}
|
|
121657
121661
|
|
|
121658
|
-
|
|
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
|
-
|
|
121667
|
-
|
|
121668
|
-
|
|
121669
|
-
|
|
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
|
-
|
|
121673
|
-
|
|
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"> > </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));
|
package/dist/bundle.esm.js
CHANGED
|
@@ -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.#
|
|
121654
|
+
this.#renderer(); // 초기 렌더링 (빵 부스러기 데이터는 아직 없음)
|
|
121655
|
+
this.#generateBreadcrumb(); // 빵 부스러기 데이터 생성 및 최종 렌더링 트리거
|
|
121652
121656
|
}
|
|
121653
121657
|
|
|
121654
|
-
|
|
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
|
-
|
|
121663
|
-
|
|
121664
|
-
|
|
121665
|
-
|
|
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
|
-
|
|
121669
|
-
|
|
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"> > </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));
|
package/dist/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.#
|
|
13
|
+
this.#renderer(); // 초기 렌더링 (빵 부스러기 데이터는 아직 없음)
|
|
14
|
+
this.#generateBreadcrumb(); // 빵 부스러기 데이터 생성 및 최종 렌더링 트리거
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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"> > </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
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.#
|
|
13
|
+
this.#renderer(); // 초기 렌더링 (빵 부스러기 데이터는 아직 없음)
|
|
14
|
+
this.#generateBreadcrumb(); // 빵 부스러기 데이터 생성 및 최종 렌더링 트리거
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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"> > </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);
|