Merge branch 'master' of http://yongjoon.cho@vcs.iten.co.kr:9999/itnAdmin/fairnet
BIN
src/main/webapp/kofair_case_seed/adm/images/layout/icon_ip.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/main/webapp/kofair_case_seed/adm/images/layout/icon_time.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/main/webapp/kofair_case_seed/adm/images/layout/icon_user.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src/main/webapp/kofair_case_seed/adm/images/layout/logo.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 569 B |
|
After Width: | Height: | Size: 957 B |
BIN
src/main/webapp/kofair_case_seed/adm/images/layout/menu_file.png
Normal file
|
After Width: | Height: | Size: 633 B |
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/main/webapp/kofair_case_seed/adm/images/layout/menu_site.png
Normal file
|
After Width: | Height: | Size: 576 B |
BIN
src/main/webapp/kofair_case_seed/adm/images/layout/menu_skin.png
Normal file
|
After Width: | Height: | Size: 738 B |
|
After Width: | Height: | Size: 379 B |
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>관리자페이지</title>
|
||||
|
||||
|
||||
<!-- css -->
|
||||
<link rel="stylesheet" href="/kofair_case_seed/css/reset.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/css/font.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/adm/style/layout.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/adm/style/common.css">
|
||||
|
||||
|
||||
<!-- script -->
|
||||
<script src="/kofair_case_seed/script/lib/jquery-3.5.0.js"></script>
|
||||
<script src="/kofair_case_seed/adm/scripts/common.js"></script>
|
||||
<script src="/kofair_case_seed/adm/scripts/layout.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrap">
|
||||
|
||||
<div data-include-path="/kofair_case_seed/adm/layout/leftmenu.html"></div>
|
||||
|
||||
<div class="contents">
|
||||
<div data-include-path="/kofair_case_seed/adm/layout/user_info.html"></div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
157
src/main/webapp/kofair_case_seed/adm/layout/leftmenu.html
Normal file
@ -0,0 +1,157 @@
|
||||
<!-- leftmenu -->
|
||||
<div class="leftmenu">
|
||||
<h1 class="logo"><a href="#none"><img src="/kofair_case_seed/adm/images/layout/logo.png" alt="FAIR 한국공정거래조정원 분쟁조정사건처리시스템"></a></h1>
|
||||
<nav class="menu">
|
||||
<ul class="menu_ul">
|
||||
<li class="depth01_li">
|
||||
<button type="button" class="menu_title">
|
||||
<i class="icon menu_icon dashboard"></i> Dashboard
|
||||
</button>
|
||||
</li>
|
||||
<li class="depth01_li">
|
||||
|
||||
<!-- depth01 -->
|
||||
<button type="button" class="menu_title">
|
||||
<i class="icon menu_icon site"></i> 사이트관리
|
||||
</button>
|
||||
<!-- //depth01 -->
|
||||
|
||||
<!-- depth02 -->
|
||||
<ul class="depth02_ul">
|
||||
<li class="depth02_li">
|
||||
<a href="#none">기본관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">메뉴관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">레이아웃 속성관리</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- //depth02 -->
|
||||
|
||||
</li>
|
||||
|
||||
<li class="depth01_li">
|
||||
|
||||
<!-- depth01 -->
|
||||
<button type="button" class="menu_title">
|
||||
<i class="icon menu_icon function"></i> 기능관리
|
||||
</button>
|
||||
<!-- //depth01 -->
|
||||
|
||||
<!-- depth02 -->
|
||||
<ul class="depth02_ul">
|
||||
<li class="depth02_li">
|
||||
<a href="#none">기본관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">메뉴관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">레이아웃 속성관리</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- //depth02 -->
|
||||
|
||||
</li>
|
||||
|
||||
<li class="depth01_li">
|
||||
|
||||
<!-- depth01 -->
|
||||
<button type="button" class="menu_title">
|
||||
<i class="icon menu_icon file"></i> 파일관리
|
||||
</button>
|
||||
<!-- //depth01 -->
|
||||
|
||||
<!-- depth02 -->
|
||||
<ul class="depth02_ul">
|
||||
<li class="depth02_li">
|
||||
<a href="#none">기본관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">메뉴관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">레이아웃 속성관리</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- //depth02 -->
|
||||
|
||||
</li>
|
||||
|
||||
<li class="depth01_li active">
|
||||
|
||||
<!-- depth01 -->
|
||||
<button type="button" class="menu_title">
|
||||
<i class="icon menu_icon skin"></i> 스킨관리
|
||||
</button>
|
||||
<!-- //depth01 -->
|
||||
|
||||
<!-- depth02 -->
|
||||
<ul class="depth02_ul">
|
||||
<li class="depth02_li">
|
||||
<a href="#none">기본관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">메뉴관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">레이아웃 속성관리</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- //depth02 -->
|
||||
|
||||
</li>
|
||||
|
||||
<li class="depth01_li">
|
||||
|
||||
<!-- depth01 -->
|
||||
<button type="button" class="menu_title">
|
||||
<i class="icon menu_icon statistics"></i> 통계관리
|
||||
</button>
|
||||
<!-- //depth01 -->
|
||||
|
||||
<!-- depth02 -->
|
||||
<ul class="depth02_ul">
|
||||
<li class="depth02_li">
|
||||
<a href="#none">기본관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">메뉴관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">레이아웃 속성관리</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- //depth02 -->
|
||||
|
||||
</li>
|
||||
|
||||
<li class="depth01_li">
|
||||
|
||||
<!-- depth01 -->
|
||||
<button type="button" class="menu_title">
|
||||
<i class="icon menu_icon error"></i> 장애관리
|
||||
</button>
|
||||
<!-- //depth01 -->
|
||||
|
||||
<!-- depth02 -->
|
||||
<ul class="depth02_ul">
|
||||
<li class="depth02_li">
|
||||
<a href="#none">기본관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">메뉴관리</a>
|
||||
</li>
|
||||
<li class="depth02_li">
|
||||
<a href="#none">레이아웃 속성관리</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- //depth02 -->
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- //leftmenu -->
|
||||
26
src/main/webapp/kofair_case_seed/adm/layout/user_info.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!-- user_info -->
|
||||
<div class="user_info">
|
||||
|
||||
<ul class="user_util_ul">
|
||||
<li><i class="icon time"></i> 최종접속일시 : 2024-07-08 14:51</li>
|
||||
<li><i class="icon ip"></i> IP : 218.234.66.153</li>
|
||||
<li><i class="icon timeout"></i> 로그인 타임아웃 : <span class="color_orange fw_bold">59:10</span><button type="button" class="btn btn_text orange_border light_orange_fill btn_extension">연장</button></li>
|
||||
</ul>
|
||||
|
||||
<div class="area_right">
|
||||
<ul class="user_info_ul">
|
||||
<li>
|
||||
<select name="" id="">
|
||||
<option value="">관리팀</option>
|
||||
<option value="">관리팀2</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<i class="icon user"></i> 슈퍼관리자(honggildong235)
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn_text gray_fill btn_logout">로그아웃</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- //user_info -->
|
||||
@ -0,0 +1,18 @@
|
||||
// header, footer 공통 영역 불러오기
|
||||
window.addEventListener('load', function () {
|
||||
var allElements = document.getElementsByTagName('*');
|
||||
Array.prototype.forEach.call(allElements, function (el) {
|
||||
var includePath = el.dataset.includePath;
|
||||
if (includePath) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
el.outerHTML = this.responseText;
|
||||
leftMenu();
|
||||
}
|
||||
};
|
||||
xhttp.open('GET', includePath, true);
|
||||
xhttp.send();
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
function leftMenu() {
|
||||
|
||||
// 첫 화면에 하위메뉴 닫기
|
||||
$('.depth01_li').removeClass('active');
|
||||
$('.depth02_ul').slideUp(0);
|
||||
|
||||
|
||||
$('.menu_title').click(function () {
|
||||
|
||||
// 대시보드 메뉴는 하위메뉴가 없을 수도 있으니 클릭 이벤트 막음
|
||||
if ($(this).closest(".depth01_li").index() !== 0) {
|
||||
$(this).closest('.depth01_li').toggleClass('active');
|
||||
$(this).next('.depth02_ul').toggleClass('active');
|
||||
$(this).next('.depth02_ul').slideToggle(400);
|
||||
$(this).closest('.depth01_li').siblings('.depth01_li').removeClass('active');
|
||||
$(this).closest('.depth01_li').siblings('.depth01_li').find('.depth02_ul').slideUp(400);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,230 @@
|
||||
@charset "utf-8";
|
||||
|
||||
|
||||
/* 버튼 */
|
||||
.btn_wrap{display:flex;margin:40px 0 0 0;justify-content:space-between;align-items:center;gap:10px;}
|
||||
.btn_wrap.right{justify-content:flex-end;}
|
||||
.btn_wrap.left{justify-content:flex-start;}
|
||||
.btn_wrap.center{justify-content:center;}
|
||||
|
||||
.btn{display:inline-block;color:#333;border-radius:4px;transition:all 0.2s ease-in-out;}
|
||||
.btn.round{border-radius:100%;}
|
||||
.btn.only_icon{padding:0;}
|
||||
|
||||
.btn:hover{box-shadow:0 0 5px rgba(0,0,0,0.3);transition:all 0.2s ease-in-out;}
|
||||
|
||||
.btn.orange_border{border:1px solid #fd6e18;color:#fd6e18;}
|
||||
.btn.light_orange_fill{background:#fdeade;color:#fd6e18;}
|
||||
|
||||
.btn.gray_fill{background:#adadb5;color:#fff;}
|
||||
|
||||
/* 아이콘 */
|
||||
.icon{display:inline-block;}
|
||||
|
||||
|
||||
/* 텍스트 */
|
||||
.fw_light{font-weight:300 !important;}
|
||||
.fw_regular{font-weight:400 !important;}
|
||||
.fw_medium{font-weight:500 !important;}
|
||||
.fw_bold{font-weight:700 !important;}
|
||||
.fw_extrabold{font-weight:800 !important;}
|
||||
|
||||
.color_333{color:#333 !important;}
|
||||
.color_666{color:#666 !important;}
|
||||
.color_orange{color:#fa6718 !important;}
|
||||
|
||||
|
||||
/* 노출/숨김 */
|
||||
.show{display:block !important;}
|
||||
.hide{display:none !important;}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* 간격 */
|
||||
.p0 {padding: 0px!important;}
|
||||
.p5 {padding: 5px!important;}
|
||||
|
||||
.pt5 {padding-top: 5px!important;}
|
||||
.pt10 {padding-top: 10px!important;}
|
||||
.pt15 {padding-top: 15px!important;}
|
||||
.pt20 {padding-top: 20px!important;}
|
||||
.pt25 {padding-top: 25px!important;}
|
||||
.pt30 {padding-top: 30px!important;}
|
||||
.pt35 {padding-top: 35px!important;}
|
||||
.pt40 {padding-top: 40px!important;}
|
||||
.pt45 {padding-top: 45px!important;}
|
||||
.pt50 {padding-top: 50px!important;}
|
||||
|
||||
.pr0 {padding-right: 0px!important;}
|
||||
.pr20 {padding-right: 20px!important;}
|
||||
|
||||
.pb5 {padding-bottom: 5px!important;}
|
||||
.pb10 {padding-bottom: 10px!important;}
|
||||
.pb15 {padding-bottom: 15px!important;}
|
||||
.pb20 {padding-bottom: 20px!important;}
|
||||
.pb25 {padding-bottom: 25px!important;}
|
||||
.pb30 {padding-bottom: 30px!important;}
|
||||
.pb35 {padding-bottom: 35px!important;}
|
||||
.pb40 {padding-bottom: 40px!important;}
|
||||
.pb45 {padding-bottom: 45px!important;}
|
||||
.pb50 {padding-bottom: 50px!important;}
|
||||
|
||||
.pl0 {padding-left: 0px!important;}
|
||||
.pl15 {padding-left: 15px!important;}
|
||||
.pl20 {padding-left: 20px!important;}
|
||||
|
||||
.m20 {margin: 20px!important;}
|
||||
|
||||
.mt-1 {margin-top: -1px!important;}
|
||||
.mt0 {margin-top: 0px!important;}
|
||||
.mt5 {margin-top: 5px!important;}
|
||||
.mt10 {margin-top: 10px!important;}
|
||||
.mt15 {margin-top: 15px!important;}
|
||||
.mt20 {margin-top: 20px!important;}
|
||||
.mt25 {margin-top: 25px!important;}
|
||||
.mt30 {margin-top: 30px!important;}
|
||||
.mt35 {margin-top: 35px!important;}
|
||||
.mt40 {margin-top: 40px!important;}
|
||||
.mt45 {margin-top: 45px!important;}
|
||||
.mt50 {margin-top: 50px!important;}
|
||||
.mt60 {margin-top: 60px!important;}
|
||||
.mt70 {margin-top: 70px!important;}
|
||||
.mt80 {margin-top: 80px!important;}
|
||||
.mt90 {margin-top: 90px!important;}
|
||||
.mt100 {margin-top: 100px!important;}
|
||||
|
||||
.mr0 {margin-right: 0px!important;}
|
||||
.mr3 {margin-right: 3px!important;}
|
||||
.mr5 {margin-right: 5px!important;}
|
||||
.mr10 {margin-right: 10px!important;}
|
||||
.mr15 {margin-right: 15px!important;}
|
||||
.mr20 {margin-right: 20px!important;}
|
||||
.mr25 {margin-right: 25px!important;}
|
||||
.mr30 {margin-right: 30px!important;}
|
||||
.mr35 {margin-right: 35px!important;}
|
||||
.mr40 {margin-right: 40px!important;}
|
||||
.mr45 {margin-right: 45px!important;}
|
||||
.mr50 {margin-right: 50px!important;}
|
||||
.mr60 {margin-right: 60px!important;}
|
||||
.mr70 {margin-right: 70px!important;}
|
||||
.mr80 {margin-right: 80px!important;}
|
||||
.mr90 {margin-right: 90px!important;}
|
||||
.mr100 {margin-right: 100px!important;}
|
||||
|
||||
.mb-4 {margin-bottom: -4px!important;}
|
||||
.mb0 {margin-bottom: 0px!important;}
|
||||
.mb1 {margin-bottom: 1px!important;}
|
||||
.mb5 {margin-bottom: 5px!important;}
|
||||
.mb10 {margin-bottom: 10px!important;}
|
||||
.mb15 {margin-bottom: 15px!important;}
|
||||
.mb18 {margin-bottom: 18px!important;}
|
||||
.mb20 {margin-bottom: 20px!important;}
|
||||
.mb25 {margin-bottom: 25px!important;}
|
||||
.mb30 {margin-bottom: 30px!important;}
|
||||
.mb35 {margin-bottom: 35px!important;}
|
||||
.mb40 {margin-bottom: 40px!important;}
|
||||
.mb45 {margin-bottom: 45px!important;}
|
||||
.mb50 {margin-bottom: 50px!important;}
|
||||
.mb60 {margin-bottom: 60px!important;}
|
||||
.mb70 {margin-bottom: 70px!important;}
|
||||
.mb80 {margin-bottom: 80px!important;}
|
||||
.mb90 {margin-bottom: 90px!important;}
|
||||
.mb100 {margin-bottom: 100px!important;}
|
||||
|
||||
.ml0 {margin-left: 0px!important;}
|
||||
.ml5 {margin-left: 5px!important;}
|
||||
.ml10 {margin-left: 10px!important;}
|
||||
.ml15 {margin-left: 15px!important;}
|
||||
.ml20 {margin-left: 20px!important;}
|
||||
.ml25 {margin-left: 25px!important;}
|
||||
.ml30 {margin-left: 30px!important;}
|
||||
.ml35 {margin-left: 35px!important;}
|
||||
.ml40 {margin-left: 40px!important;}
|
||||
.ml45 {margin-left: 45px!important;}
|
||||
.ml50 {margin-left: 50px!important;}
|
||||
.ml60 {margin-left: 60px!important;}
|
||||
.ml70 {margin-left: 70px!important;}
|
||||
.ml80 {margin-left: 80px!important;}
|
||||
.ml90 {margin-left: 90px!important;}
|
||||
.ml100 {margin-left: 100px!important;}
|
||||
|
||||
/* 너비, 높이 */
|
||||
.w100per {width: 100% !important;}
|
||||
.w99per {width: 99%;}
|
||||
.w95per {width: 95%;}
|
||||
.w90per {width: 90%;}
|
||||
.w85per {width: 85%;}
|
||||
.w80per {width: 80%;}
|
||||
.w75per {width: 75%;}
|
||||
.w70per {width: 70%;}
|
||||
.w65per {width: 65%;}
|
||||
.w60per {width: 60%;}
|
||||
.w50per {width: 50%;}
|
||||
.w55per {width: 55%;}
|
||||
.w45per {width: 45%;}
|
||||
.w40per {width: 40%;}
|
||||
.w35per {width: 35%;}
|
||||
.w33per {width: 33.3333333%;}
|
||||
.w30per {width: 30%;}
|
||||
.w25per {width: 25%;}
|
||||
.w20per {width: 20%;}
|
||||
.w19per {width: 19%;}
|
||||
.w18per {width: 18%;}
|
||||
.w17per {width: 17%;}
|
||||
.w16per {width: 16%;}
|
||||
.w15per {width: 15%;}
|
||||
.w14per {width: 14%;}
|
||||
.w13per {width: 13%;}
|
||||
.w12per {width: 12%;}
|
||||
.w11per {width: 11%;}
|
||||
.w10per {width: 10%;}
|
||||
.w9per {width: 9%;}
|
||||
.w8per {width: 8%;}
|
||||
.w7per {width: 7%;}
|
||||
.w6per {width: 6%;}
|
||||
.w5per {width: 5%;}
|
||||
.w4per {width: 4%;}
|
||||
.w3per {width: 3%;}
|
||||
.w2per {width: 2%;}
|
||||
.w1per {width: 1%;}
|
||||
|
||||
.w5 {width: 5px;}
|
||||
.w10 {width: 10px;}
|
||||
.w15 {width: 15px;}
|
||||
.w20 {width: 20px;}
|
||||
.w25 {width: 25px;}
|
||||
.w30 {width: 30px;}
|
||||
.w35 {width: 35px;}
|
||||
.w40 {width: 40px;}
|
||||
.w45 {width: 45px;}
|
||||
.w50 {width: 50px;}
|
||||
.w55 {width: 55px;}
|
||||
.w60 {width: 60px;}
|
||||
.w70 {width: 70px;}
|
||||
.w80 {width: 80px;}
|
||||
.w90 {width: 90px;}
|
||||
.w100 {width: 100px;}
|
||||
.w110 {width: 110px;}
|
||||
.w120 {width: 120px;}
|
||||
.w130 {width: 130px;}
|
||||
.w140 {width: 140px;}
|
||||
.w150 {width: 150px;}
|
||||
.w160 {width: 160px;}
|
||||
.w170 {width: 170px;}
|
||||
.w180 {width: 180px;}
|
||||
.w190 {width: 190px;}
|
||||
.w200 {width: 200px;}
|
||||
.w250 {width: 250px;}
|
||||
.w300 {width: 300px;}
|
||||
.w325 {width: 325px;}
|
||||
.w350 {width: 350px;}
|
||||
.w400 {width: 400px;}
|
||||
.w500 {width: 500px;}
|
||||
|
||||
.mw100 {min-width: 100px;}
|
||||
|
||||
.h100 {height: 100px;}
|
||||
.h100per {height: 100%;}
|
||||
@ -0,0 +1,55 @@
|
||||
@charset "utf-8";
|
||||
|
||||
.wrap{position:relative;display:flex;width:100%;min-width:1440px;}
|
||||
.contents{width:calc(100% - 300px);padding:0 30px 0 70px;}
|
||||
|
||||
/* leftmenu */
|
||||
.leftmenu{width:300px;height:100vh;box-shadow:3px 0 7px rgba(0,0,0,0.2);background:#171c70;border-radius:0 30px 30px 0;}
|
||||
.leftmenu .logo{display:flex;height:110px;border-bottom:1px solid #5d619b;justify-content:center;align-items:center;}
|
||||
.leftmenu .menu{padding:0 20px;}
|
||||
.leftmenu .menu_ul{display:flex;flex-direction:column;color:#888bbc;}
|
||||
.leftmenu .depth01_li{border-bottom:1px solid #2f337f;}
|
||||
.leftmenu .depth01_li.active{border:0;}
|
||||
.leftmenu .menu_title{position:relative;display:flex;width:100%;height:54px;font-size:18px;font-weight:500;text-align:left;color:#888bbc;letter-spacing:-0.4px;align-items:center;}
|
||||
.leftmenu .menu_title::after{position:absolute;content:"";width:10px;height:10px;border-top:2px solid #888bbc;border-right:2px solid #888bbc;transform:rotate(-45deg);right:20px;top:23px;}
|
||||
.leftmenu .active .menu_title{color:#fff;}
|
||||
.leftmenu .active .menu_title::after{border-top:2px solid #fff;border-right:2px solid #fff;transform:rotate(135deg);top:20px;}
|
||||
|
||||
.leftmenu .menu_icon{display:inline-flex;width:40px;height:40px;justify-content:center;align-items:center;background-position:top center;}
|
||||
.leftmenu .active .menu_icon{background-position:center bottom;}
|
||||
.leftmenu .menu_icon.dashboard{background-image:url(/kofair_case_seed/adm/images/layout/menu_dashboard.png);}
|
||||
.leftmenu .menu_icon.site{background-image:url(/kofair_case_seed/adm/images/layout/menu_site.png);}
|
||||
.leftmenu .menu_icon.function{background-image:url(/kofair_case_seed/adm/images/layout/menu_function.png);}
|
||||
.leftmenu .menu_icon.file{background-image:url(/kofair_case_seed/adm/images/layout/menu_file.png);}
|
||||
.leftmenu .menu_icon.skin{background-image:url(/kofair_case_seed/adm/images/layout/menu_skin.png);}
|
||||
.leftmenu .menu_icon.statistics{background-image:url(/kofair_case_seed/adm/images/layout/menu_statistics.png);}
|
||||
.leftmenu .menu_icon.error{background-image:url(/kofair_case_seed/adm/images/layout/menu_error.png);}
|
||||
|
||||
.leftmenu .depth02_ul{display:flex;width:calc(100% - 40px);font-size:16px;font-weight:400;color:#666;margin:0 auto;padding:25px 20px;background:#fff;;border-radius:5px;box-sizing:border-box;flex-direction:column;gap:15px;}
|
||||
.leftmenu .depth02_li a{position:relative;padding:0 0 0 10px;}
|
||||
.leftmenu .depth02_li a::after{position:absolute;content:"·";width:4px;height:4px;left:0;color:#666;}
|
||||
.leftmenu .depth02_li.active a,.leftmenu .depth02_li:hover a{font-weight:bold;color:#fa6718;}
|
||||
.leftmenu .depth02_li.active a::after,.leftmenu .depth02_li:hover a::after{color:#fa6718;}
|
||||
|
||||
|
||||
/* f4f5f6 */
|
||||
|
||||
.user_info{display:flex;height:110px;justify-content:space-between;align-items:center;}
|
||||
.user_info .user_util_ul,.user_info .user_util_ul li{display:inline-flex;font-size:14px;font-weight:400;color:#444;align-items:center;}
|
||||
.user_info .user_util_ul{gap:25px;}
|
||||
.user_info .user_util_ul .icon{width:20px;height:20px;background-repeat:no-repeat;background-position:center;margin:2px 6px 0 0;}
|
||||
.user_info .user_util_ul .time{background-image:url(/kofair_case_seed/adm/images/layout/icon_time.png);}
|
||||
.user_info .user_util_ul .ip{background-image:url(/kofair_case_seed/adm/images/layout/icon_ip.png);}
|
||||
.user_info .user_util_ul .timeout{background-image:url(/kofair_case_seed/adm/images/layout/icon_timeout.png);}
|
||||
.user_info .user_util_ul span{margin:0 0 0 4px;}
|
||||
.user_info .user_util_ul .btn_extension{width:43px;height:28px;margin:0 0 0 10px;}
|
||||
|
||||
.user_info .area_right{display:flex;gap:10px;}
|
||||
.user_info_ul{display:flex;height:38px;padding:0 20px 0 0;border-radius:35px; background:#f4f5f6;align-items:center;}
|
||||
.user_info_ul li{position:relative;display:inline-flex;align-items:center;line-height:1.8;}
|
||||
.user_info_ul li:first-child{margin:0 20px 0 0;}
|
||||
.user_info_ul li:first-child:after{position:absolute;content:"";width:1px;height:15px;background:#d5d5d5;right:0;}
|
||||
.user_info_ul li .icon.user{width:20px;height:20px;margin:0 4px 0 0;background:url(/kofair_case_seed/adm/images/layout/icon_user.png) no-repeat center center;}
|
||||
|
||||
.user_info_ul select{width:145px;padding:0 0 0 20px;font-size:16px;border:0;background:transparent url(/kofair_case_seed/adm/images/layout/icon_userinfo_select.png) no-repeat calc(100% - 20px) center;}
|
||||
.user_info .btn_logout{width:94px;height:38px;font-size:16px;border-radius:40px;background:#adadb5;}
|
||||
1
src/main/webapp/kofair_case_seed/adm/style/style.css
Normal file
@ -0,0 +1 @@
|
||||
@charset "utf-8";
|
||||
@ -1,10 +1,10 @@
|
||||
/* reset 파일 정리 */
|
||||
|
||||
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video {margin: 0;padding: 0;border: 0;font-size: 100%;font: inherit;vertical-align: baseline;color: inherit;font-weight: inherit;font-family: 'Noto Sans KR', sans-serif;}
|
||||
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video {margin: 0;padding: 0;border: 0;font-size: 100%;font: inherit;vertical-align: baseline;color: inherit;font-weight: inherit;font-family: 'Noto Sans KR', sans-serif; word-break: keep-all;}
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section {display: block; font-family: 'Noto Sans KR', sans-serif; margin: 0; padding: 0; border: 0; font-size: 100%;}
|
||||
body {min-height: 100vh;line-height: 1.3;-webkit-font-smoothing: antialiased;}
|
||||
body {min-height: 100vh;line-height: 1.4;-webkit-font-smoothing: antialiased;letter-spacing:-0.5px;;}
|
||||
ol,ul,li {list-style: none;}
|
||||
p, h1, h2, h3, h4, h5, h6 {margin: 0; padding: 0;letter-spacing: -0.5px;}
|
||||
p, h1, h2, h3, h4, h5, h6 {margin: 0; padding: 0;letter-spacing: -0.35px;}
|
||||
h1, h2, h3, h4, h5, h6, button, input, label{line-height:1.1;}
|
||||
a {color: inherit; text-decoration: none; display: block;}
|
||||
/* a:focus:active:hover{outline: 0} */
|
||||
@ -21,6 +21,7 @@ button[disabled],html input[disabled] {cursor: default;}
|
||||
input[type="checkbox"],input[type="radio"] {box-sizing: border-box; padding: 0; *height: 13px; *width: 13px;}
|
||||
/* 아이폰 기본적으로 적용되어 있는 버튼 css 변경 */
|
||||
input{-webkit-appearance: button;}
|
||||
input::placeholder{font-size:15px;font-weight:300;font-family:'Noto Sans KR', sans-serif;}
|
||||
/* 사파리5, 크롬에서 기본적으로 적용되어 있는 css 변경 */
|
||||
input[type="search"] {-webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box;}
|
||||
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration {-webkit-appearance: none;}
|
||||
@ -36,10 +37,9 @@ textarea{color: #666;font-size: 20px;font-weight: 300; font-family: 'Noto Sans K
|
||||
input::placeholder,input[type="text"]::placeholder,input[type="password"]::placeholder,input[type="text"]:-ms-input-placeholder,input[type="password"]:-ms-input-placeholder{color: #666 !important; font-size: 16px !important;}
|
||||
input[type="text"]:focus::placeholder,input[type="password"]:focus::placeholder{color: transparent;}
|
||||
|
||||
input:disabled,input[disabled="disabled"],input:read-only,input[readonly="readonly"]{background-color: #eee !important; color: #aaa !important; font-size: 16px;}
|
||||
button:disabled,button[disabled="disabled"]{background-color: #eee !important; color: #aaa !important; border: none !important;}
|
||||
select:disabled,select[disabled="disabled"]{background-color: #eee !important; color: #aaa !important; border: none !important;}
|
||||
|
||||
input:disabled,input[disabled="disabled"],input:read-only,input[readonly="readonly"]{background-color: #f8f9fa !important; color: #aaa !important; border:1px solid #d8d8d8 !important; font-size: 16px;}
|
||||
button:disabled,button[disabled="disabled"]{background-color: #f8f9fa !important; color: #aaa !important; border: 1px solid #d8d8d8 !important;}
|
||||
select:disabled,select[disabled="disabled"],select.read-only,select[readonly="readonly"]{background-color: #f8f9fa !important; color: #aaa !important; border: 1px solid #d8d8d8 !important;pointer-events:none;}
|
||||
*,*::before,*::after {box-sizing: border-box;}
|
||||
|
||||
:target{scroll-margin-block: 5ex;}
|
||||
|
||||
@ -0,0 +1,528 @@
|
||||
:root {
|
||||
--duet-color-primary: #005fcc;
|
||||
--duet-color-text: #333;
|
||||
--duet-color-text-active: #fff;
|
||||
--duet-color-placeholder: #666;
|
||||
--duet-color-button: #f5f5f5;
|
||||
--duet-color-surface: #fff;
|
||||
--duet-color-overlay: rgba(0, 0, 0, 0.8);
|
||||
--duet-color-border: #333;
|
||||
|
||||
--duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--duet-font-normal: 400;
|
||||
--duet-font-bold: 600;
|
||||
|
||||
--duet-radius: 4px;
|
||||
--duet-z-index: 600;
|
||||
}
|
||||
|
||||
.duet-date *,
|
||||
.duet-date *::before,
|
||||
.duet-date *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
width: auto
|
||||
}
|
||||
|
||||
.duet-date {
|
||||
box-sizing: border-box;
|
||||
color: var(--duet-color-text);
|
||||
display: block;
|
||||
font-family: var(--duet-font);
|
||||
margin: 0;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.duet-date__input {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: var(--duet-color-surface);
|
||||
border: 1px solid var(--duet-color-border, var(--duet-color-text));
|
||||
border-radius: var(--duet-radius);
|
||||
color: var(--duet-color-text);
|
||||
float: none;
|
||||
font-family: var(--duet-font);
|
||||
font-size: 100%;
|
||||
line-height: normal;
|
||||
padding: 14px 60px 14px 14px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.duet-date__input:focus {
|
||||
border-color: var(--duet-color-primary);
|
||||
box-shadow: 0 0 0 1px var(--duet-color-primary);
|
||||
outline: 0
|
||||
}
|
||||
|
||||
.duet-date__input::-webkit-input-placeholder {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.duet-date__input:-moz-placeholder {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.duet-date__input:-ms-input-placeholder {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.duet-date__input-wrapper {
|
||||
position: relative;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.duet-date__toggle {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-webkit-user-select: none;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: var(--duet-color-button);
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
border-bottom-right-radius: var(--duet-radius);
|
||||
border-top-right-radius: var(--duet-radius);
|
||||
box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
color: var(--duet-color-text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: calc(100% - 2px);
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
user-select: none;
|
||||
width: 48px;
|
||||
z-index: 2
|
||||
}
|
||||
|
||||
.duet-date__toggle:focus {
|
||||
box-shadow: 0 0 0 2px var(--duet-color-primary);
|
||||
outline: 0
|
||||
}
|
||||
|
||||
.duet-date__toggle-icon {
|
||||
display: flex;
|
||||
flex-basis: 100%;
|
||||
justify-content: center;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.duet-date__dialog {
|
||||
display: flex;
|
||||
left: 0;
|
||||
min-width: 320px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
transform: scale(0.96) translateZ(0) translateY(-20px);
|
||||
transform-origin: top right;
|
||||
transition: transform 300ms ease, opacity 300ms ease, visibility 300ms ease;
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
will-change: transform, opacity, visibility;
|
||||
z-index: var(--duet-z-index)
|
||||
}
|
||||
|
||||
@media (max-width: 35.9375em) {
|
||||
.duet-date__dialog {
|
||||
background: var(--duet-color-overlay);
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: translateZ(0);
|
||||
transform-origin: bottom center
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__dialog.is-left {
|
||||
left: auto;
|
||||
right: 0;
|
||||
width: auto
|
||||
}
|
||||
|
||||
.duet-date__dialog.is-active {
|
||||
opacity: 1;
|
||||
transform: scale(1.0001) translateZ(0) translateY(0);
|
||||
visibility: visible
|
||||
}
|
||||
|
||||
.duet-date__dialog-content {
|
||||
background: var(--duet-color-surface);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--duet-radius);
|
||||
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.1);
|
||||
margin-left: auto;
|
||||
margin-top: 8px;
|
||||
max-width: 310px;
|
||||
min-width: 290px;
|
||||
padding: 16px 16px 20px;
|
||||
position: relative;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
z-index: var(--duet-z-index)
|
||||
}
|
||||
|
||||
@media (max-width: 35.9375em) {
|
||||
.duet-date__dialog-content {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
border-top-left-radius: var(--duet-radius);
|
||||
border-top-right-radius: var(--duet-radius);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
max-width: none;
|
||||
min-height: 26em;
|
||||
opacity: 0;
|
||||
padding: 0 8% 20px;
|
||||
position: absolute;
|
||||
transform: translateZ(0) translateY(100%);
|
||||
transition: transform 400ms ease, opacity 400ms ease, visibility 400ms ease;
|
||||
visibility: hidden;
|
||||
will-change: transform, opacity, visibility
|
||||
}
|
||||
|
||||
.is-active .duet-date__dialog-content {
|
||||
opacity: 1;
|
||||
transform: translateZ(0) translateY(0);
|
||||
visibility: visible
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
color: var(--duet-color-text);
|
||||
font-size: 1rem;
|
||||
font-weight: var(--duet-font-normal);
|
||||
line-height: 1.25;
|
||||
text-align: center;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.duet-date__table-header {
|
||||
font-size: 0.75rem;
|
||||
font-weight: var(--duet-font-bold);
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.25;
|
||||
padding-bottom: 8px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase
|
||||
}
|
||||
|
||||
.duet-date__cell {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.duet-date__day {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--duet-color-text);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-family: var(--duet-font);
|
||||
font-size: 0.875rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: var(--duet-font-normal);
|
||||
height: 36px;
|
||||
line-height: 1.25;
|
||||
padding: 0 0 1px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 36px;
|
||||
z-index: 1
|
||||
}
|
||||
|
||||
.duet-date__day.is-today {
|
||||
box-shadow: 0 0 0 1px var(--duet-color-primary);
|
||||
position: relative;
|
||||
z-index: 200
|
||||
}
|
||||
|
||||
.duet-date__day:hover::before,
|
||||
.duet-date__day.is-today::before {
|
||||
background: var(--duet-color-primary);
|
||||
border-radius: 50%;
|
||||
bottom: 0;
|
||||
content: "";
|
||||
left: 0;
|
||||
opacity: 0.06;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.duet-date__day[aria-pressed=true],
|
||||
.duet-date__day:focus {
|
||||
background: var(--duet-color-primary);
|
||||
box-shadow: none;
|
||||
color: var(--duet-color-text-active);
|
||||
outline: 0
|
||||
}
|
||||
|
||||
.duet-date__day:active {
|
||||
background: var(--duet-color-primary);
|
||||
box-shadow: 0 0 5px var(--duet-color-primary);
|
||||
color: var(--duet-color-text-active);
|
||||
z-index: 200
|
||||
}
|
||||
|
||||
.duet-date__day:focus {
|
||||
box-shadow: 0 0 5px var(--duet-color-primary);
|
||||
z-index: 200
|
||||
}
|
||||
|
||||
.duet-date__day:not(.is-month) {
|
||||
box-shadow: none
|
||||
}
|
||||
|
||||
.duet-date__day:not(.is-month),
|
||||
.duet-date__day[aria-disabled=true] {
|
||||
background: transparent;
|
||||
color: var(--duet-color-text);
|
||||
cursor: default;
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
.duet-date__day[aria-disabled=true].is-today {
|
||||
box-shadow: 0 0 0 1px var(--duet-color-primary)
|
||||
}
|
||||
|
||||
.duet-date__day[aria-disabled=true].is-today:focus {
|
||||
box-shadow: 0 0 5px var(--duet-color-primary);
|
||||
background: var(--duet-color-primary);
|
||||
color: var(--duet-color-text-active)
|
||||
}
|
||||
|
||||
.duet-date__day[aria-disabled=true]:not(.is-today)::before {
|
||||
display: none
|
||||
}
|
||||
|
||||
.duet-date__day.is-outside {
|
||||
background: var(--duet-color-button);
|
||||
box-shadow: none;
|
||||
color: var(--duet-color-text);
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
.duet-date__day.is-outside::before {
|
||||
display: none
|
||||
}
|
||||
|
||||
.duet-date__header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.duet-date__nav {
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
.duet-date__prev,
|
||||
.duet-date__next {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: var(--duet-color-button);
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--duet-color-text);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
padding: 0;
|
||||
transition: background-color 300ms ease;
|
||||
width: 32px
|
||||
}
|
||||
|
||||
@media (max-width: 35.9375em) {
|
||||
|
||||
.duet-date__prev,
|
||||
.duet-date__next {
|
||||
height: 40px;
|
||||
width: 40px
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__prev:focus,
|
||||
.duet-date__next:focus {
|
||||
box-shadow: 0 0 0 2px var(--duet-color-primary);
|
||||
outline: 0
|
||||
}
|
||||
|
||||
.duet-date__prev:active:focus,
|
||||
.duet-date__next:active:focus {
|
||||
box-shadow: none
|
||||
}
|
||||
|
||||
.duet-date__prev:disabled,
|
||||
.duet-date__next:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
.duet-date__prev svg,
|
||||
.duet-date__next svg {
|
||||
margin: 0 auto
|
||||
}
|
||||
|
||||
.duet-date__select {
|
||||
display: inline-flex;
|
||||
margin-top: 4px;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.duet-date__select span {
|
||||
margin-right: 4px
|
||||
}
|
||||
|
||||
.duet-date__select select {
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 2
|
||||
}
|
||||
|
||||
.duet-date__select select:focus+.duet-date__select-label {
|
||||
box-shadow: 0 0 0 2px var(--duet-color-primary)
|
||||
}
|
||||
|
||||
.duet-date__select-label {
|
||||
align-items: center;
|
||||
border-radius: var(--duet-radius);
|
||||
color: var(--duet-color-text);
|
||||
display: flex;
|
||||
font-size: 1.25rem;
|
||||
font-weight: var(--duet-font-bold);
|
||||
line-height: 1.25;
|
||||
padding: 0 4px 0 8px;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1
|
||||
}
|
||||
|
||||
.duet-date__select-label svg {
|
||||
width: 16px;
|
||||
height: 16px
|
||||
}
|
||||
|
||||
.duet-date__mobile {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
margin-left: -10%;
|
||||
overflow: hidden;
|
||||
padding: 12px 20px;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 120%
|
||||
}
|
||||
|
||||
@media (min-width: 36em) {
|
||||
.duet-date__mobile {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: -8px;
|
||||
width: auto
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__mobile-heading {
|
||||
display: inline-block;
|
||||
font-weight: var(--duet-font-bold);
|
||||
max-width: 84%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
@media (min-width: 36em) {
|
||||
.duet-date__mobile-heading {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__close {
|
||||
-webkit-appearance: none;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: var(--duet-color-button);
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--duet-color-text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
width: 24px
|
||||
}
|
||||
|
||||
@media (min-width: 36em) {
|
||||
.duet-date__close {
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__close:focus {
|
||||
box-shadow: 0 0 0 2px var(--duet-color-primary);
|
||||
outline: none
|
||||
}
|
||||
|
||||
@media (min-width: 36em) {
|
||||
.duet-date__close:focus {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__close svg {
|
||||
margin: 0 auto
|
||||
}
|
||||
|
||||
.duet-date__vhidden {
|
||||
border: 0;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1px
|
||||
}
|
||||
@ -0,0 +1,696 @@
|
||||
self,
|
||||
function () {
|
||||
var t = new Set("annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" "));
|
||||
|
||||
function n(n) {
|
||||
var i = t.has(n);
|
||||
return n = /^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(n), !i && n
|
||||
}
|
||||
|
||||
function i(t) {
|
||||
var n = t.isConnected;
|
||||
if (void 0 !== n) return n;
|
||||
for (; t && !(t.__CE_isImportDocument || t instanceof Document);) t = t.parentNode || (window.ShadowRoot && t instanceof ShadowRoot ? t.host : void 0);
|
||||
return !(!t || !(t.__CE_isImportDocument || t instanceof Document))
|
||||
}
|
||||
|
||||
function e(t, n) {
|
||||
for (; n && n !== t && !n.nextSibling;) n = n.parentNode;
|
||||
return n && n !== t ? n.nextSibling : null
|
||||
}
|
||||
|
||||
function o(t, n, i) {
|
||||
i = void 0 === i ? new Set : i;
|
||||
for (var r = t; r;) {
|
||||
if (r.nodeType === Node.ELEMENT_NODE) {
|
||||
var f = r;
|
||||
n(f);
|
||||
var c = f.localName;
|
||||
if ("link" === c && "import" === f.getAttribute("rel")) {
|
||||
if ((r = f.import) instanceof Node && !i.has(r))
|
||||
for (i.add(r), r = r.firstChild; r; r = r.nextSibling) o(r, n, i);
|
||||
r = e(t, f);
|
||||
continue
|
||||
}
|
||||
if ("template" === c) {
|
||||
r = e(t, f);
|
||||
continue
|
||||
}
|
||||
if (f = f.__CE_shadowRoot)
|
||||
for (f = f.firstChild; f; f = f.nextSibling) o(f, n, i)
|
||||
}
|
||||
r = r.firstChild ? r.firstChild : e(t, r)
|
||||
}
|
||||
}
|
||||
|
||||
function r(t, n, i) {
|
||||
t[n] = i
|
||||
}
|
||||
|
||||
function f() {
|
||||
this.a = new Map, this.g = new Map, this.c = [], this.f = [], this.b = !1
|
||||
}
|
||||
|
||||
function c(t, n) {
|
||||
t.b && o(n, (function (n) {
|
||||
return u(t, n)
|
||||
}))
|
||||
}
|
||||
|
||||
function u(t, n) {
|
||||
if (t.b && !n.__CE_patched) {
|
||||
n.__CE_patched = !0;
|
||||
for (var i = 0; i < t.c.length; i++) t.c[i](n);
|
||||
for (i = 0; i < t.f.length; i++) t.f[i](n)
|
||||
}
|
||||
}
|
||||
|
||||
function s(t, n) {
|
||||
var i = [];
|
||||
for (o(n, (function (t) {
|
||||
return i.push(t)
|
||||
})), n = 0; n < i.length; n++) {
|
||||
var e = i[n];
|
||||
1 === e.__CE_state ? t.connectedCallback(e) : l(t, e)
|
||||
}
|
||||
}
|
||||
|
||||
function a(t, n) {
|
||||
var i = [];
|
||||
for (o(n, (function (t) {
|
||||
return i.push(t)
|
||||
})), n = 0; n < i.length; n++) {
|
||||
var e = i[n];
|
||||
1 === e.__CE_state && t.disconnectedCallback(e)
|
||||
}
|
||||
}
|
||||
|
||||
function h(t, n, i) {
|
||||
var e = (i = void 0 === i ? {} : i).u || new Set,
|
||||
r = i.i || function (n) {
|
||||
return l(t, n)
|
||||
},
|
||||
f = [];
|
||||
if (o(n, (function (n) {
|
||||
if ("link" === n.localName && "import" === n.getAttribute("rel")) {
|
||||
var i = n.import;
|
||||
i instanceof Node && (i.__CE_isImportDocument = !0, i.__CE_hasRegistry = !0), i && "complete" === i.readyState ? i.__CE_documentLoadHandled = !0 : n.addEventListener("load", (function () {
|
||||
var i = n.import;
|
||||
if (!i.__CE_documentLoadHandled) {
|
||||
i.__CE_documentLoadHandled = !0;
|
||||
var o = new Set(e);
|
||||
o.delete(i), h(t, i, {
|
||||
u: o,
|
||||
i: r
|
||||
})
|
||||
}
|
||||
}))
|
||||
} else f.push(n)
|
||||
}), e), t.b)
|
||||
for (n = 0; n < f.length; n++) u(t, f[n]);
|
||||
for (n = 0; n < f.length; n++) r(f[n])
|
||||
}
|
||||
|
||||
function l(t, n) {
|
||||
if (void 0 === n.__CE_state) {
|
||||
var e = n.ownerDocument;
|
||||
if ((e.defaultView || e.__CE_isImportDocument && e.__CE_hasRegistry) && (e = t.a.get(n.localName))) {
|
||||
e.constructionStack.push(n);
|
||||
var o = e.constructorFunction;
|
||||
try {
|
||||
try {
|
||||
if (new o !== n) throw Error("The custom element constructor did not produce the element being upgraded.")
|
||||
} finally {
|
||||
e.constructionStack.pop()
|
||||
}
|
||||
} catch (t) {
|
||||
throw n.__CE_state = 2, t
|
||||
}
|
||||
if (n.__CE_state = 1, n.__CE_definition = e, e.attributeChangedCallback)
|
||||
for (e = e.observedAttributes, o = 0; o < e.length; o++) {
|
||||
var r = e[o],
|
||||
f = n.getAttribute(r);
|
||||
null !== f && t.attributeChangedCallback(n, r, null, f, null)
|
||||
}
|
||||
i(n) && t.connectedCallback(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function d(t) {
|
||||
var n = document;
|
||||
this.c = t, this.a = n, this.b = void 0, h(this.c, this.a), "loading" === this.a.readyState && (this.b = new MutationObserver(this.f.bind(this)), this.b.observe(this.a, {
|
||||
childList: !0,
|
||||
subtree: !0
|
||||
}))
|
||||
}
|
||||
|
||||
function w(t) {
|
||||
t.b && t.b.disconnect()
|
||||
}
|
||||
|
||||
function v() {
|
||||
var t = this;
|
||||
this.b = this.a = void 0, this.c = new Promise((function (n) {
|
||||
t.b = n, t.a && n(t.a)
|
||||
}))
|
||||
}
|
||||
|
||||
function m(t) {
|
||||
if (t.a) throw Error("Already resolved.");
|
||||
t.a = void 0, t.b && t.b(void 0)
|
||||
}
|
||||
|
||||
function b(t) {
|
||||
this.c = !1, this.a = t, this.j = new Map, this.f = function (t) {
|
||||
return t()
|
||||
}, this.b = !1, this.g = [], this.o = new d(t)
|
||||
}
|
||||
f.prototype.connectedCallback = function (t) {
|
||||
var n = t.__CE_definition;
|
||||
n.connectedCallback && n.connectedCallback.call(t)
|
||||
}, f.prototype.disconnectedCallback = function (t) {
|
||||
var n = t.__CE_definition;
|
||||
n.disconnectedCallback && n.disconnectedCallback.call(t)
|
||||
}, f.prototype.attributeChangedCallback = function (t, n, i, e, o) {
|
||||
var r = t.__CE_definition;
|
||||
r.attributeChangedCallback && -1 < r.observedAttributes.indexOf(n) && r.attributeChangedCallback.call(t, n, i, e, o)
|
||||
}, d.prototype.f = function (t) {
|
||||
var n = this.a.readyState;
|
||||
for ("interactive" !== n && "complete" !== n || w(this), n = 0; n < t.length; n++)
|
||||
for (var i = t[n].addedNodes, e = 0; e < i.length; e++) h(this.c, i[e])
|
||||
}, b.prototype.l = function (t, i) {
|
||||
var e = this;
|
||||
if (!(i instanceof Function)) throw new TypeError("Custom element constructors must be functions.");
|
||||
if (!n(t)) throw new SyntaxError("The element name '" + t + "' is not valid.");
|
||||
if (this.a.a.get(t)) throw Error("A custom element with name '" + t + "' has already been defined.");
|
||||
if (this.c) throw Error("A custom element is already being defined.");
|
||||
this.c = !0;
|
||||
try {
|
||||
var o = function (t) {
|
||||
var n = r[t];
|
||||
if (void 0 !== n && !(n instanceof Function)) throw Error("The '" + t + "' callback must be a function.");
|
||||
return n
|
||||
},
|
||||
r = i.prototype;
|
||||
if (!(r instanceof Object)) throw new TypeError("The custom element constructor's prototype is not an object.");
|
||||
var f = o("connectedCallback"),
|
||||
c = o("disconnectedCallback"),
|
||||
u = o("adoptedCallback"),
|
||||
s = o("attributeChangedCallback"),
|
||||
a = i.observedAttributes || []
|
||||
} catch (t) {
|
||||
return
|
||||
} finally {
|
||||
this.c = !1
|
||||
}(function (t, n, i) {
|
||||
t.a.set(n, i), t.g.set(i.constructorFunction, i)
|
||||
})(this.a, t, i = {
|
||||
localName: t,
|
||||
constructorFunction: i,
|
||||
connectedCallback: f,
|
||||
disconnectedCallback: c,
|
||||
adoptedCallback: u,
|
||||
attributeChangedCallback: s,
|
||||
observedAttributes: a,
|
||||
constructionStack: []
|
||||
}), this.g.push(i), this.b || (this.b = !0, this.f((function () {
|
||||
return function (t) {
|
||||
if (!1 !== t.b) {
|
||||
t.b = !1;
|
||||
for (var n = t.g, i = [], e = new Map, o = 0; o < n.length; o++) e.set(n[o].localName, []);
|
||||
for (h(t.a, document, {
|
||||
i: function (n) {
|
||||
if (void 0 === n.__CE_state) {
|
||||
var o = n.localName,
|
||||
r = e.get(o);
|
||||
r ? r.push(n) : t.a.a.get(o) && i.push(n)
|
||||
}
|
||||
}
|
||||
}), o = 0; o < i.length; o++) l(t.a, i[o]);
|
||||
for (; 0 < n.length;) {
|
||||
var r = n.shift();
|
||||
o = r.localName, r = e.get(r.localName);
|
||||
for (var f = 0; f < r.length; f++) l(t.a, r[f]);
|
||||
(o = t.j.get(o)) && m(o)
|
||||
}
|
||||
}
|
||||
}(e)
|
||||
})))
|
||||
}, b.prototype.i = function (t) {
|
||||
h(this.a, t)
|
||||
}, b.prototype.get = function (t) {
|
||||
if (t = this.a.a.get(t)) return t.constructorFunction
|
||||
}, b.prototype.m = function (t) {
|
||||
if (!n(t)) return Promise.reject(new SyntaxError("'" + t + "' is not a valid custom element name."));
|
||||
var i = this.j.get(t);
|
||||
return i || (i = new v, this.j.set(t, i), this.a.a.get(t) && !this.g.some((function (n) {
|
||||
return n.localName === t
|
||||
})) && m(i)), i.c
|
||||
}, b.prototype.s = function (t) {
|
||||
w(this.o);
|
||||
var n = this.f;
|
||||
this.f = function (i) {
|
||||
return t((function () {
|
||||
return n(i)
|
||||
}))
|
||||
}
|
||||
}, window.CustomElementRegistry = b, b.prototype.define = b.prototype.l, b.prototype.upgrade = b.prototype.i, b.prototype.get = b.prototype.get, b.prototype.whenDefined = b.prototype.m, b.prototype.polyfillWrapFlushCallback = b.prototype.s;
|
||||
var E = window.Document.prototype.createElement,
|
||||
p = window.Document.prototype.createElementNS,
|
||||
g = window.Document.prototype.importNode,
|
||||
y = window.Document.prototype.prepend,
|
||||
C = window.Document.prototype.append,
|
||||
T = window.DocumentFragment.prototype.prepend,
|
||||
j = window.DocumentFragment.prototype.append,
|
||||
D = window.Node.prototype.cloneNode,
|
||||
N = window.Node.prototype.appendChild,
|
||||
A = window.Node.prototype.insertBefore,
|
||||
O = window.Node.prototype.removeChild,
|
||||
M = window.Node.prototype.replaceChild,
|
||||
k = Object.getOwnPropertyDescriptor(window.Node.prototype, "textContent"),
|
||||
L = window.Element.prototype.attachShadow,
|
||||
S = Object.getOwnPropertyDescriptor(window.Element.prototype, "innerHTML"),
|
||||
F = window.Element.prototype.getAttribute,
|
||||
H = window.Element.prototype.setAttribute,
|
||||
x = window.Element.prototype.removeAttribute,
|
||||
z = window.Element.prototype.getAttributeNS,
|
||||
P = window.Element.prototype.setAttributeNS,
|
||||
R = window.Element.prototype.removeAttributeNS,
|
||||
$ = window.Element.prototype.insertAdjacentElement,
|
||||
_ = window.Element.prototype.insertAdjacentHTML,
|
||||
B = window.Element.prototype.prepend,
|
||||
I = window.Element.prototype.append,
|
||||
U = window.Element.prototype.before,
|
||||
W = window.Element.prototype.after,
|
||||
q = window.Element.prototype.replaceWith,
|
||||
G = window.Element.prototype.remove,
|
||||
J = window.HTMLElement,
|
||||
K = Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, "innerHTML"),
|
||||
Q = window.HTMLElement.prototype.insertAdjacentElement,
|
||||
V = window.HTMLElement.prototype.insertAdjacentHTML,
|
||||
X = new function () {};
|
||||
|
||||
function Y(t, n, e) {
|
||||
function o(n) {
|
||||
return function (e) {
|
||||
for (var o = [], r = 0; r < arguments.length; ++r) o[r] = arguments[r];
|
||||
r = [];
|
||||
for (var f = [], c = 0; c < o.length; c++) {
|
||||
var u = o[c];
|
||||
if (u instanceof Element && i(u) && f.push(u), u instanceof DocumentFragment)
|
||||
for (u = u.firstChild; u; u = u.nextSibling) r.push(u);
|
||||
else r.push(u)
|
||||
}
|
||||
for (n.apply(this, o), o = 0; o < f.length; o++) a(t, f[o]);
|
||||
if (i(this))
|
||||
for (o = 0; o < r.length; o++)(f = r[o]) instanceof Element && s(t, f)
|
||||
}
|
||||
}
|
||||
void 0 !== e.h && (n.prepend = o(e.h)), void 0 !== e.append && (n.append = o(e.append))
|
||||
}
|
||||
var Z = window.customElements;
|
||||
if (!Z || Z.forcePolyfill || "function" != typeof Z.define || "function" != typeof Z.get) {
|
||||
var tt = new f;
|
||||
! function () {
|
||||
var t = tt;
|
||||
window.HTMLElement = function () {
|
||||
function n() {
|
||||
var n = this.constructor,
|
||||
i = t.g.get(n);
|
||||
if (!i) throw Error("The custom element being constructed was not registered with `customElements`.");
|
||||
var e = i.constructionStack;
|
||||
if (0 === e.length) return e = E.call(document, i.localName), Object.setPrototypeOf(e, n.prototype), e.__CE_state = 1, e.__CE_definition = i, u(t, e), e;
|
||||
var o = e[i = e.length - 1];
|
||||
if (o === X) throw Error("The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.");
|
||||
return e[i] = X, Object.setPrototypeOf(o, n.prototype), u(t, o), o
|
||||
}
|
||||
return Object.defineProperty(n.prototype = J.prototype, "constructor", {
|
||||
writable: !0,
|
||||
configurable: !0,
|
||||
enumerable: !1,
|
||||
value: n
|
||||
}), n
|
||||
}()
|
||||
}(),
|
||||
function () {
|
||||
var t = tt;
|
||||
r(Document.prototype, "createElement", (function (n) {
|
||||
if (this.__CE_hasRegistry) {
|
||||
var i = t.a.get(n);
|
||||
if (i) return new i.constructorFunction
|
||||
}
|
||||
return n = E.call(this, n), u(t, n), n
|
||||
})), r(Document.prototype, "importNode", (function (n, i) {
|
||||
return n = g.call(this, n, !!i), this.__CE_hasRegistry ? h(t, n) : c(t, n), n
|
||||
})), r(Document.prototype, "createElementNS", (function (n, i) {
|
||||
if (this.__CE_hasRegistry && (null === n || "http://www.w3.org/1999/xhtml" === n)) {
|
||||
var e = t.a.get(i);
|
||||
if (e) return new e.constructorFunction
|
||||
}
|
||||
return n = p.call(this, n, i), u(t, n), n
|
||||
})), Y(t, Document.prototype, {
|
||||
h: y,
|
||||
append: C
|
||||
})
|
||||
}(), Y(tt, DocumentFragment.prototype, {
|
||||
h: T,
|
||||
append: j
|
||||
}),
|
||||
function () {
|
||||
function t(t, e) {
|
||||
Object.defineProperty(t, "textContent", {
|
||||
enumerable: e.enumerable,
|
||||
configurable: !0,
|
||||
get: e.get,
|
||||
set: function (t) {
|
||||
if (this.nodeType === Node.TEXT_NODE) e.set.call(this, t);
|
||||
else {
|
||||
var o = void 0;
|
||||
if (this.firstChild) {
|
||||
var r = this.childNodes,
|
||||
f = r.length;
|
||||
if (0 < f && i(this)) {
|
||||
o = Array(f);
|
||||
for (var c = 0; c < f; c++) o[c] = r[c]
|
||||
}
|
||||
}
|
||||
if (e.set.call(this, t), o)
|
||||
for (t = 0; t < o.length; t++) a(n, o[t])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
var n = tt;
|
||||
r(Node.prototype, "insertBefore", (function (t, e) {
|
||||
if (t instanceof DocumentFragment) {
|
||||
var o = Array.prototype.slice.apply(t.childNodes);
|
||||
if (t = A.call(this, t, e), i(this))
|
||||
for (e = 0; e < o.length; e++) s(n, o[e]);
|
||||
return t
|
||||
}
|
||||
return o = i(t), e = A.call(this, t, e), o && a(n, t), i(this) && s(n, t), e
|
||||
})), r(Node.prototype, "appendChild", (function (t) {
|
||||
if (t instanceof DocumentFragment) {
|
||||
var e = Array.prototype.slice.apply(t.childNodes);
|
||||
if (t = N.call(this, t), i(this))
|
||||
for (var o = 0; o < e.length; o++) s(n, e[o]);
|
||||
return t
|
||||
}
|
||||
return e = i(t), o = N.call(this, t), e && a(n, t), i(this) && s(n, t), o
|
||||
})), r(Node.prototype, "cloneNode", (function (t) {
|
||||
return t = D.call(this, !!t), this.ownerDocument.__CE_hasRegistry ? h(n, t) : c(n, t), t
|
||||
})), r(Node.prototype, "removeChild", (function (t) {
|
||||
var e = i(t),
|
||||
o = O.call(this, t);
|
||||
return e && a(n, t), o
|
||||
})), r(Node.prototype, "replaceChild", (function (t, e) {
|
||||
if (t instanceof DocumentFragment) {
|
||||
var o = Array.prototype.slice.apply(t.childNodes);
|
||||
if (t = M.call(this, t, e), i(this))
|
||||
for (a(n, e), e = 0; e < o.length; e++) s(n, o[e]);
|
||||
return t
|
||||
}
|
||||
o = i(t);
|
||||
var r = M.call(this, t, e),
|
||||
f = i(this);
|
||||
return f && a(n, e), o && a(n, t), f && s(n, t), r
|
||||
})), k && k.get ? t(Node.prototype, k) : function (t, n) {
|
||||
t.b = !0, t.c.push(n)
|
||||
}(n, (function (n) {
|
||||
t(n, {
|
||||
enumerable: !0,
|
||||
configurable: !0,
|
||||
get: function () {
|
||||
for (var t = [], n = 0; n < this.childNodes.length; n++) {
|
||||
var i = this.childNodes[n];
|
||||
i.nodeType !== Node.COMMENT_NODE && t.push(i.textContent)
|
||||
}
|
||||
return t.join("")
|
||||
},
|
||||
set: function (t) {
|
||||
for (; this.firstChild;) O.call(this, this.firstChild);
|
||||
null != t && "" !== t && N.call(this, document.createTextNode(t))
|
||||
}
|
||||
})
|
||||
}))
|
||||
}(),
|
||||
function () {
|
||||
function t(t, n) {
|
||||
Object.defineProperty(t, "innerHTML", {
|
||||
enumerable: n.enumerable,
|
||||
configurable: !0,
|
||||
get: n.get,
|
||||
set: function (t) {
|
||||
var e = this,
|
||||
r = void 0;
|
||||
if (i(this) && (r = [], o(this, (function (t) {
|
||||
t !== e && r.push(t)
|
||||
}))), n.set.call(this, t), r)
|
||||
for (var u = 0; u < r.length; u++) {
|
||||
var s = r[u];
|
||||
1 === s.__CE_state && f.disconnectedCallback(s)
|
||||
}
|
||||
return this.ownerDocument.__CE_hasRegistry ? h(f, this) : c(f, this), t
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function n(t, n) {
|
||||
r(t, "insertAdjacentElement", (function (t, e) {
|
||||
var o = i(e);
|
||||
return t = n.call(this, t, e), o && a(f, e), i(t) && s(f, e), t
|
||||
}))
|
||||
}
|
||||
|
||||
function e(t, n) {
|
||||
function i(t, n) {
|
||||
for (var i = []; t !== n; t = t.nextSibling) i.push(t);
|
||||
for (n = 0; n < i.length; n++) h(f, i[n])
|
||||
}
|
||||
r(t, "insertAdjacentHTML", (function (t, e) {
|
||||
if ("beforebegin" === (t = t.toLowerCase())) {
|
||||
var o = this.previousSibling;
|
||||
n.call(this, t, e), i(o || this.parentNode.firstChild, this)
|
||||
} else if ("afterbegin" === t) o = this.firstChild, n.call(this, t, e), i(this.firstChild, o);
|
||||
else if ("beforeend" === t) o = this.lastChild, n.call(this, t, e), i(o || this.firstChild, null);
|
||||
else {
|
||||
if ("afterend" !== t) throw new SyntaxError("The value provided (" + t + ") is not one of 'beforebegin', 'afterbegin', 'beforeend', or 'afterend'.");
|
||||
o = this.nextSibling, n.call(this, t, e), i(this.nextSibling, o)
|
||||
}
|
||||
}))
|
||||
}
|
||||
var f = tt;
|
||||
L && r(Element.prototype, "attachShadow", (function (t) {
|
||||
t = L.call(this, t);
|
||||
var n = f;
|
||||
if (n.b && !t.__CE_patched) {
|
||||
t.__CE_patched = !0;
|
||||
for (var i = 0; i < n.c.length; i++) n.c[i](t)
|
||||
}
|
||||
return this.__CE_shadowRoot = t
|
||||
})), S && S.get ? t(Element.prototype, S) : K && K.get ? t(HTMLElement.prototype, K) : function (t, n) {
|
||||
t.b = !0, t.f.push(n)
|
||||
}(f, (function (n) {
|
||||
t(n, {
|
||||
enumerable: !0,
|
||||
configurable: !0,
|
||||
get: function () {
|
||||
return D.call(this, !0).innerHTML
|
||||
},
|
||||
set: function (t) {
|
||||
var n = "template" === this.localName,
|
||||
i = n ? this.content : this,
|
||||
e = p.call(document, this.namespaceURI, this.localName);
|
||||
for (e.innerHTML = t; 0 < i.childNodes.length;) O.call(i, i.childNodes[0]);
|
||||
for (t = n ? e.content : e; 0 < t.childNodes.length;) N.call(i, t.childNodes[0])
|
||||
}
|
||||
})
|
||||
})), r(Element.prototype, "setAttribute", (function (t, n) {
|
||||
if (1 !== this.__CE_state) return H.call(this, t, n);
|
||||
var i = F.call(this, t);
|
||||
H.call(this, t, n), n = F.call(this, t), f.attributeChangedCallback(this, t, i, n, null)
|
||||
})), r(Element.prototype, "setAttributeNS", (function (t, n, i) {
|
||||
if (1 !== this.__CE_state) return P.call(this, t, n, i);
|
||||
var e = z.call(this, t, n);
|
||||
P.call(this, t, n, i), i = z.call(this, t, n), f.attributeChangedCallback(this, n, e, i, t)
|
||||
})), r(Element.prototype, "removeAttribute", (function (t) {
|
||||
if (1 !== this.__CE_state) return x.call(this, t);
|
||||
var n = F.call(this, t);
|
||||
x.call(this, t), null !== n && f.attributeChangedCallback(this, t, n, null, null)
|
||||
})), r(Element.prototype, "removeAttributeNS", (function (t, n) {
|
||||
if (1 !== this.__CE_state) return R.call(this, t, n);
|
||||
var i = z.call(this, t, n);
|
||||
R.call(this, t, n);
|
||||
var e = z.call(this, t, n);
|
||||
i !== e && f.attributeChangedCallback(this, n, i, e, t)
|
||||
})), Q ? n(HTMLElement.prototype, Q) : $ ? n(Element.prototype, $) : console.warn("Custom Elements: `Element#insertAdjacentElement` was not patched."), V ? e(HTMLElement.prototype, V) : _ ? e(Element.prototype, _) : console.warn("Custom Elements: `Element#insertAdjacentHTML` was not patched."), Y(f, Element.prototype, {
|
||||
h: B,
|
||||
append: I
|
||||
}),
|
||||
function (t) {
|
||||
function n(n) {
|
||||
return function (e) {
|
||||
for (var o = [], r = 0; r < arguments.length; ++r) o[r] = arguments[r];
|
||||
r = [];
|
||||
for (var f = [], c = 0; c < o.length; c++) {
|
||||
var u = o[c];
|
||||
if (u instanceof Element && i(u) && f.push(u), u instanceof DocumentFragment)
|
||||
for (u = u.firstChild; u; u = u.nextSibling) r.push(u);
|
||||
else r.push(u)
|
||||
}
|
||||
for (n.apply(this, o), o = 0; o < f.length; o++) a(t, f[o]);
|
||||
if (i(this))
|
||||
for (o = 0; o < r.length; o++)(f = r[o]) instanceof Element && s(t, f)
|
||||
}
|
||||
}
|
||||
var e = Element.prototype;
|
||||
void 0 !== U && (e.before = n(U)), void 0 !== U && (e.after = n(W)), void 0 !== q && r(e, "replaceWith", (function (n) {
|
||||
for (var e = [], o = 0; o < arguments.length; ++o) e[o] = arguments[o];
|
||||
o = [];
|
||||
for (var r = [], f = 0; f < e.length; f++) {
|
||||
var c = e[f];
|
||||
if (c instanceof Element && i(c) && r.push(c), c instanceof DocumentFragment)
|
||||
for (c = c.firstChild; c; c = c.nextSibling) o.push(c);
|
||||
else o.push(c)
|
||||
}
|
||||
for (f = i(this), q.apply(this, e), e = 0; e < r.length; e++) a(t, r[e]);
|
||||
if (f)
|
||||
for (a(t, this), e = 0; e < o.length; e++)(r = o[e]) instanceof Element && s(t, r)
|
||||
})), void 0 !== G && r(e, "remove", (function () {
|
||||
var n = i(this);
|
||||
G.call(this), n && a(t, this)
|
||||
}))
|
||||
}(f)
|
||||
}(), document.__CE_hasRegistry = !0;
|
||||
var nt = new b(tt);
|
||||
Object.defineProperty(window, "customElements", {
|
||||
configurable: !0,
|
||||
enumerable: !0,
|
||||
value: nt
|
||||
})
|
||||
}
|
||||
}(), "string" != typeof document.baseURI && Object.defineProperty(Document.prototype, "baseURI", {
|
||||
enumerable: !0,
|
||||
configurable: !0,
|
||||
get: function () {
|
||||
var t = document.querySelector("base");
|
||||
return t && t.href ? t.href : document.URL
|
||||
}
|
||||
}), "function" != typeof window.CustomEvent && (window.CustomEvent = function (t, n) {
|
||||
n = n || {
|
||||
bubbles: !1,
|
||||
cancelable: !1,
|
||||
detail: void 0
|
||||
};
|
||||
var i = document.createEvent("CustomEvent");
|
||||
return i.initCustomEvent(t, n.bubbles, n.cancelable, n.detail), i
|
||||
}, window.CustomEvent.prototype = window.Event.prototype),
|
||||
function (t, n, i) {
|
||||
t.composedPath || (t.composedPath = function () {
|
||||
if (this.path) return this.path;
|
||||
var t = this.target;
|
||||
for (this.path = []; null !== t.parentNode;) this.path.push(t), t = t.parentNode;
|
||||
return this.path.push(n, i), this.path
|
||||
})
|
||||
}(Event.prototype, document, window),
|
||||
/*!
|
||||
Element.closest and Element.matches
|
||||
https://github.com/jonathantneal/closest
|
||||
Creative Commons Zero v1.0 Universal
|
||||
*/
|
||||
function (t) {
|
||||
"function" != typeof t.matches && (t.matches = t.msMatchesSelector || t.mozMatchesSelector || t.webkitMatchesSelector || function (t) {
|
||||
t = (this.document || this.ownerDocument).querySelectorAll(t);
|
||||
for (var n = 0; t[n] && t[n] !== this;) ++n;
|
||||
return !!t[n]
|
||||
}), "function" != typeof t.closest && (t.closest = function (t) {
|
||||
for (var n = this; n && 1 === n.nodeType;) {
|
||||
if (n.matches(t)) return n;
|
||||
n = n.parentNode
|
||||
}
|
||||
return null
|
||||
})
|
||||
}(window.Element.prototype),
|
||||
/*!
|
||||
Element.getRootNode()
|
||||
*/
|
||||
function (t) {
|
||||
function n(t) {
|
||||
return (t = i(t)) && 11 === t.nodeType ? n(t.host) : t
|
||||
}
|
||||
|
||||
function i(t) {
|
||||
return t && t.parentNode ? i(t.parentNode) : t
|
||||
}
|
||||
"function" != typeof t.getRootNode && (t.getRootNode = function (t) {
|
||||
return t && t.composed ? n(this) : i(this)
|
||||
})
|
||||
}(Element.prototype),
|
||||
/*!
|
||||
Element.isConnected()
|
||||
*/
|
||||
function (t) {
|
||||
"isConnected" in t || Object.defineProperty(t, "isConnected", {
|
||||
configurable: !0,
|
||||
enumerable: !0,
|
||||
get: function () {
|
||||
var t = this.getRootNode({
|
||||
composed: !0
|
||||
});
|
||||
return t && 9 === t.nodeType
|
||||
}
|
||||
})
|
||||
}(Element.prototype), [Element.prototype, CharacterData.prototype, DocumentType.prototype].forEach((function (t) {
|
||||
t.hasOwnProperty("remove") || Object.defineProperty(t, "remove", {
|
||||
configurable: !0,
|
||||
enumerable: !0,
|
||||
writable: !0,
|
||||
value: function () {
|
||||
null !== this.parentNode && this.parentNode.removeChild(this)
|
||||
}
|
||||
})
|
||||
})),
|
||||
function (t) {
|
||||
"classList" in t || Object.defineProperty(t, "classList", {
|
||||
get: function () {
|
||||
var t = this,
|
||||
n = (t.getAttribute("class") || "").replace(/^\s+|\s$/g, "").split(/\s+/g);
|
||||
|
||||
function i() {
|
||||
n.length > 0 ? t.setAttribute("class", n.join(" ")) : t.removeAttribute("class")
|
||||
}
|
||||
return "" === n[0] && n.splice(0, 1), n.toggle = function (t, e) {
|
||||
void 0 !== e ? e ? n.add(t) : n.remove(t) : -1 !== n.indexOf(t) ? n.splice(n.indexOf(t), 1) : n.push(t), i()
|
||||
}, n.add = function () {
|
||||
for (var t = [].slice.call(arguments), e = 0, o = t.length; e < o; e++) - 1 === n.indexOf(t[e]) && n.push(t[e]);
|
||||
i()
|
||||
}, n.remove = function () {
|
||||
for (var t = [].slice.call(arguments), e = 0, o = t.length; e < o; e++) - 1 !== n.indexOf(t[e]) && n.splice(n.indexOf(t[e]), 1);
|
||||
i()
|
||||
}, n.item = function (t) {
|
||||
return n[t]
|
||||
}, n.contains = function (t) {
|
||||
return -1 !== n.indexOf(t)
|
||||
}, n.replace = function (t, e) {
|
||||
-1 !== n.indexOf(t) && n.splice(n.indexOf(t), 1, e), i()
|
||||
}, n.value = t.getAttribute("class") || "", n
|
||||
}
|
||||
})
|
||||
}(Element.prototype),
|
||||
/*!
|
||||
DOMTokenList
|
||||
*/
|
||||
function (t) {
|
||||
try {
|
||||
document.body.classList.add()
|
||||
} catch (e) {
|
||||
var n = t.add,
|
||||
i = t.remove;
|
||||
t.add = function () {
|
||||
for (var t = 0; t < arguments.length; t++) n.call(this, arguments[t])
|
||||
}, t.remove = function () {
|
||||
for (var t = 0; t < arguments.length; t++) i.call(this, arguments[t])
|
||||
}
|
||||
}
|
||||
}(DOMTokenList.prototype);
|
||||
@ -0,0 +1,64 @@
|
||||
import {
|
||||
p as e,
|
||||
w as a,
|
||||
d as t,
|
||||
N as s,
|
||||
a as i,
|
||||
b as r
|
||||
} from "./index-a3afd6e1.js";
|
||||
(() => {
|
||||
e.t = a.__cssshim;
|
||||
const r = Array.from(t.querySelectorAll("script")).find((e => new RegExp(`/${s}(\\.esm)?\\.js($|\\?|#)`).test(e.src) || e.getAttribute("data-stencil-namespace") === s)),
|
||||
n = {};
|
||||
return n.resourcesUrl = new URL(".", new URL(r.getAttribute("data-resources-url") || r.src, a.location.href)).href, ((e, i) => {
|
||||
const r = `__sc_import_${s.replace(/\s|-/g,"_")}`;
|
||||
try {
|
||||
a[r] = new Function("w", `return import(w);//${Math.random()}`)
|
||||
} catch (s) {
|
||||
const n = new Map;
|
||||
a[r] = s => {
|
||||
const o = new URL(s, e).href;
|
||||
let c = n.get(o);
|
||||
if (!c) {
|
||||
const e = t.createElement("script");
|
||||
e.type = "module", e.crossOrigin = i.crossOrigin, e.src = URL.createObjectURL(new Blob([`import * as m from '${o}'; window.${r}.m = m;`], {
|
||||
type: "application/javascript"
|
||||
})), c = new Promise((t => {
|
||||
e.onload = () => {
|
||||
t(a[r].m), e.remove()
|
||||
}
|
||||
})), n.set(o, c), t.head.appendChild(e)
|
||||
}
|
||||
return c
|
||||
}
|
||||
}
|
||||
})(n.resourcesUrl, r), a.customElements ? i(n) : __sc_import_duet("./dom-fb6a473e.js").then((() => n))
|
||||
})().then((e => r([
|
||||
["duet-date-picker", [
|
||||
[0, "duet-date-picker", {
|
||||
name: [1],
|
||||
identifier: [1],
|
||||
disabled: [516],
|
||||
role: [1],
|
||||
direction: [1],
|
||||
required: [4],
|
||||
value: [1537],
|
||||
min: [1],
|
||||
max: [1],
|
||||
firstDayOfWeek: [2, "first-day-of-week"],
|
||||
localization: [16],
|
||||
dateAdapter: [16],
|
||||
isDateDisabled: [16],
|
||||
activeFocus: [32],
|
||||
focusedDay: [32],
|
||||
open: [32],
|
||||
setFocus: [64],
|
||||
show: [64],
|
||||
hide: [64]
|
||||
},
|
||||
[
|
||||
[6, "click", "handleDocumentClick"]
|
||||
]
|
||||
]
|
||||
]]
|
||||
], e)));
|
||||
6862
src/main/webapp/kofair_case_seed/script/plugin/datapicker/duet.js
Normal file
@ -0,0 +1 @@
|
||||
System.register(["./index-7f002a21.system.js"],(function(e,r){"use strict";var t,n,i,s,a,c;return{setters:[function(e){t=e.p;n=e.w;i=e.d;s=e.N;a=e.a;c=e.b}],execute:function(){var e=function(e){return"__sc_import_"+e.replace(/\s|-/g,"_")};var o=function(){{t.$cssShim$=n.__cssshim}var e=Array.from(i.querySelectorAll("script")).find((function(e){return new RegExp("/"+s+"(\\.esm)?\\.js($|\\?|#)").test(e.src)||e.getAttribute("data-stencil-namespace")===s}));var c=r.meta.url;var o={};if(c!==""){o.resourcesUrl=new URL(".",c).href}else{o.resourcesUrl=new URL(".",new URL(e.getAttribute("data-resources-url")||e.src,n.location.href)).href;{u(o.resourcesUrl,e)}if(!n.customElements){return r.import("./dom-9370655f.system.js").then((function(){return o}))}}return a(o)};var u=function(r,t){var a=e(s);try{n[a]=new Function("w","return import(w);//"+Math.random())}catch(e){var c=new Map;n[a]=function(e){var s=new URL(e,r).href;var o=c.get(s);if(!o){var u=i.createElement("script");u.type="module";u.crossOrigin=t.crossOrigin;u.src=URL.createObjectURL(new Blob(["import * as m from '"+s+"'; window."+a+".m = m;"],{type:"application/javascript"}));o=new Promise((function(e){u.onload=function(){e(n[a].m);u.remove()}}));c.set(s,o);i.head.appendChild(u)}return o}}};o().then((function(e){return c([["duet-date-picker.system",[[0,"duet-date-picker",{name:[1],identifier:[1],disabled:[516],role:[1],direction:[1],required:[4],value:[1537],min:[1],max:[1],firstDayOfWeek:[2,"first-day-of-week"],localization:[16],dateAdapter:[16],isDateDisabled:[16],activeFocus:[32],focusedDay:[32],open:[32],setFocus:[64],show:[64],hide:[64]},[[6,"click","handleDocumentClick"]]]]]],e)}))}}}));
|
||||
@ -0,0 +1,486 @@
|
||||
const e = "duet";
|
||||
let t = !1;
|
||||
const l = "undefined" != typeof window ? window : {},
|
||||
n = l.CSS,
|
||||
s = l.document || {
|
||||
head: {}
|
||||
},
|
||||
o = {
|
||||
t: 0,
|
||||
l: "",
|
||||
jmp: e => e(),
|
||||
raf: e => requestAnimationFrame(e),
|
||||
ael: (e, t, l, n) => e.addEventListener(t, l, n),
|
||||
rel: (e, t, l, n) => e.removeEventListener(t, l, n),
|
||||
ce: (e, t) => new CustomEvent(e, t)
|
||||
},
|
||||
i = e => Promise.resolve(e),
|
||||
c = (() => {
|
||||
try {
|
||||
return new CSSStyleSheet, "function" == typeof (new CSSStyleSheet).replace
|
||||
} catch (e) {}
|
||||
return !1
|
||||
})(),
|
||||
r = (e, t, l) => {
|
||||
l && l.map((([l, n, s]) => {
|
||||
const i = a(e, l),
|
||||
c = u(t, s),
|
||||
r = f(l);
|
||||
o.ael(i, n, c, r), (t.o = t.o || []).push((() => o.rel(i, n, c, r)))
|
||||
}))
|
||||
},
|
||||
u = (e, t) => l => {
|
||||
try {
|
||||
256 & e.t ? e.i[t](l) : (e.u = e.u || []).push([t, l])
|
||||
} catch (e) {
|
||||
le(e)
|
||||
}
|
||||
},
|
||||
a = (e, t) => 4 & t ? s : e,
|
||||
f = e => 0 != (2 & e),
|
||||
$ = new WeakMap,
|
||||
d = e => "sc-" + e.$,
|
||||
h = {},
|
||||
y = e => "object" == (e = typeof e) || "function" === e,
|
||||
p = (e, t, ...l) => {
|
||||
let n = null,
|
||||
s = null,
|
||||
o = !1,
|
||||
i = !1,
|
||||
c = [];
|
||||
const r = t => {
|
||||
for (let l = 0; l < t.length; l++) n = t[l], Array.isArray(n) ? r(n) : null != n && "boolean" != typeof n && ((o = "function" != typeof e && !y(n)) && (n += ""), o && i ? c[c.length - 1].h += n : c.push(o ? m(null, n) : n), i = o)
|
||||
};
|
||||
if (r(l), t) {
|
||||
t.key && (s = t.key); {
|
||||
const e = t.className || t.class;
|
||||
e && (t.class = "object" != typeof e ? e : Object.keys(e).filter((t => e[t])).join(" "))
|
||||
}
|
||||
}
|
||||
if ("function" == typeof e) return e(null === t ? {} : t, c, b);
|
||||
const u = m(e, null);
|
||||
return u.p = t, c.length > 0 && (u.m = c), u.g = s, u
|
||||
},
|
||||
m = (e, t) => ({
|
||||
t: 0,
|
||||
v: e,
|
||||
h: t,
|
||||
j: null,
|
||||
m: null,
|
||||
p: null,
|
||||
g: null
|
||||
}),
|
||||
w = {},
|
||||
b = {
|
||||
forEach: (e, t) => e.map(g).forEach(t),
|
||||
map: (e, t) => e.map(g).map(t).map(v)
|
||||
},
|
||||
g = e => ({
|
||||
vattrs: e.p,
|
||||
vchildren: e.m,
|
||||
vkey: e.g,
|
||||
vname: e.k,
|
||||
vtag: e.v,
|
||||
vtext: e.h
|
||||
}),
|
||||
v = e => {
|
||||
if ("function" == typeof e.vtag) {
|
||||
const t = Object.assign({}, e.vattrs);
|
||||
return e.vkey && (t.key = e.vkey), e.vname && (t.name = e.vname), p(e.vtag, t, ...e.vchildren || [])
|
||||
}
|
||||
const t = m(e.vtag, e.vtext);
|
||||
return t.p = e.vattrs, t.m = e.vchildren, t.g = e.vkey, t.k = e.vname, t
|
||||
},
|
||||
j = (e, t, n, s, i, c) => {
|
||||
if (n !== s) {
|
||||
let r = te(e, t),
|
||||
u = t.toLowerCase();
|
||||
if ("class" === t) {
|
||||
const t = e.classList,
|
||||
l = S(n),
|
||||
o = S(s);
|
||||
t.remove(...l.filter((e => e && !o.includes(e)))), t.add(...o.filter((e => e && !l.includes(e))))
|
||||
} else if ("key" === t);
|
||||
else if ("ref" === t) s && s(e);
|
||||
else if (r || "o" !== t[0] || "n" !== t[1]) {
|
||||
const l = y(s);
|
||||
if ((r || l && null !== s) && !i) try {
|
||||
if (e.tagName.includes("-")) e[t] = s;
|
||||
else {
|
||||
let l = null == s ? "" : s;
|
||||
"list" === t ? r = !1 : null != n && e[t] == l || (e[t] = l)
|
||||
}
|
||||
} catch (e) {}
|
||||
null == s || !1 === s ? !1 === s && "" !== e.getAttribute(t) || e.removeAttribute(t) : (!r || 4 & c || i) && !l && e.setAttribute(t, s = !0 === s ? "" : s)
|
||||
} else t = "-" === t[2] ? t.slice(3) : te(l, u) ? u.slice(2) : u[2] + t.slice(3), n && o.rel(e, t, n, !1), s && o.ael(e, t, s, !1)
|
||||
}
|
||||
},
|
||||
k = /\s/,
|
||||
S = e => e ? e.split(k) : [],
|
||||
O = (e, t, l, n) => {
|
||||
const s = 11 === t.j.nodeType && t.j.host ? t.j.host : t.j,
|
||||
o = e && e.p || h,
|
||||
i = t.p || h;
|
||||
for (n in o) n in i || j(s, n, o[n], void 0, l, t.t);
|
||||
for (n in i) j(s, n, o[n], i[n], l, t.t)
|
||||
},
|
||||
x = (e, l, n) => {
|
||||
let o, i, c = l.m[n],
|
||||
r = 0;
|
||||
if (null !== c.h) o = c.j = s.createTextNode(c.h);
|
||||
else {
|
||||
if (t || (t = "svg" === c.v), o = c.j = s.createElementNS(t ? "http://www.w3.org/2000/svg" : "http://www.w3.org/1999/xhtml", c.v), t && "foreignObject" === c.v && (t = !1), O(null, c, t), c.m)
|
||||
for (r = 0; r < c.m.length; ++r) i = x(e, c, r), i && o.appendChild(i);
|
||||
"svg" === c.v ? t = !1 : "foreignObject" === o.tagName && (t = !0)
|
||||
}
|
||||
return o
|
||||
},
|
||||
M = (e, t, l, n, s, o) => {
|
||||
let i, c = e;
|
||||
for (; s <= o; ++s) n[s] && (i = x(null, l, s), i && (n[s].j = i, c.insertBefore(i, t)))
|
||||
},
|
||||
C = (e, t, l, n, s) => {
|
||||
for (; t <= l; ++t)(n = e[t]) && (s = n.j, E(n), s.remove())
|
||||
},
|
||||
P = (e, t) => e.v === t.v && e.g === t.g,
|
||||
_ = (e, l) => {
|
||||
const n = l.j = e.j,
|
||||
s = e.m,
|
||||
o = l.m,
|
||||
i = l.v,
|
||||
c = l.h;
|
||||
null === c ? (t = "svg" === i || "foreignObject" !== i && t, O(e, l, t), null !== s && null !== o ? ((e, t, l, n) => {
|
||||
let s, o, i = 0,
|
||||
c = 0,
|
||||
r = 0,
|
||||
u = 0,
|
||||
a = t.length - 1,
|
||||
f = t[0],
|
||||
$ = t[a],
|
||||
d = n.length - 1,
|
||||
h = n[0],
|
||||
y = n[d];
|
||||
for (; i <= a && c <= d;)
|
||||
if (null == f) f = t[++i];
|
||||
else if (null == $) $ = t[--a];
|
||||
else if (null == h) h = n[++c];
|
||||
else if (null == y) y = n[--d];
|
||||
else if (P(f, h)) _(f, h), f = t[++i], h = n[++c];
|
||||
else if (P($, y)) _($, y), $ = t[--a], y = n[--d];
|
||||
else if (P(f, y)) _(f, y), e.insertBefore(f.j, $.j.nextSibling), f = t[++i], y = n[--d];
|
||||
else if (P($, h)) _($, h), e.insertBefore($.j, f.j), $ = t[--a], h = n[++c];
|
||||
else {
|
||||
for (r = -1, u = i; u <= a; ++u)
|
||||
if (t[u] && null !== t[u].g && t[u].g === h.g) {
|
||||
r = u;
|
||||
break
|
||||
} r >= 0 ? (o = t[r], o.v !== h.v ? s = x(t && t[c], l, r) : (_(o, h), t[r] = void 0, s = o.j), h = n[++c]) : (s = x(t && t[c], l, c), h = n[++c]), s && f.j.parentNode.insertBefore(s, f.j)
|
||||
}
|
||||
i > a ? M(e, null == n[d + 1] ? null : n[d + 1].j, l, n, c, d) : c > d && C(t, i, a)
|
||||
})(n, s, l, o) : null !== o ? (null !== e.h && (n.textContent = ""), M(n, null, l, o, 0, o.length - 1)) : null !== s && C(s, 0, s.length - 1), t && "svg" === i && (t = !1)) : e.h !== c && (n.data = c)
|
||||
},
|
||||
E = e => {
|
||||
e.p && e.p.ref && e.p.ref(null), e.m && e.m.map(E)
|
||||
},
|
||||
I = e => Y(e).S,
|
||||
T = (e, t, l) => {
|
||||
const n = I(e);
|
||||
return {
|
||||
emit: e => A(n, t, {
|
||||
bubbles: !!(4 & l),
|
||||
composed: !!(2 & l),
|
||||
cancelable: !!(1 & l),
|
||||
detail: e
|
||||
})
|
||||
}
|
||||
},
|
||||
A = (e, t, l) => {
|
||||
const n = o.ce(t, l);
|
||||
return e.dispatchEvent(n), n
|
||||
},
|
||||
F = (e, t) => {
|
||||
t && !e.O && t["s-p"] && t["s-p"].push(new Promise((t => e.O = t)))
|
||||
},
|
||||
H = (e, t) => {
|
||||
if (!(4 & e.t)) return F(e, e.M), L(e, t);
|
||||
e.t |= 512
|
||||
},
|
||||
L = (e, t) => {
|
||||
const l = e.i;
|
||||
return t && (e.t |= 256, e.u && (e.u.map((([e, t]) => q(l, e, t))), e.u = null)), V(void 0, (() => N(e, l, t)))
|
||||
},
|
||||
N = async (e, t, l) => {
|
||||
const n = e.S,
|
||||
o = n["s-rc"];
|
||||
l && (e => {
|
||||
const t = e.C;
|
||||
((e, t) => {
|
||||
let l = d(t),
|
||||
n = oe.get(l);
|
||||
if (e = 11 === e.nodeType ? e : s, n)
|
||||
if ("string" == typeof n) {
|
||||
let t, o = $.get(e = e.head || e);
|
||||
o || $.set(e, o = new Set), o.has(l) || (e.host && (t = e.querySelector(`[sty-id="${l}"]`)) ? t.innerHTML = n : (t = s.createElement("style"), t.innerHTML = n, e.insertBefore(t, e.querySelector("link"))), o && o.add(l))
|
||||
} else e.adoptedStyleSheets.includes(n) || (e.adoptedStyleSheets = [...e.adoptedStyleSheets, n])
|
||||
})(e.S.getRootNode(), t)
|
||||
})(e);
|
||||
R(e, t), o && (o.map((e => e())), n["s-rc"] = void 0); {
|
||||
const t = n["s-p"],
|
||||
l = () => U(e);
|
||||
0 === t.length ? l() : (Promise.all(t).then(l), e.t |= 4, t.length = 0)
|
||||
}
|
||||
}, R = (e, t) => {
|
||||
try {
|
||||
t = t.render(), e.t |= 2, ((e, t) => {
|
||||
const l = e.S,
|
||||
n = e.C,
|
||||
s = e.P || m(null, null),
|
||||
o = (e => e && e.v === w)(t) ? t : p(null, null, t);
|
||||
n._ && (o.p = o.p || {}, n._.map((([e, t]) => o.p[t] = l[e]))), o.v = null, o.t |= 4, e.P = o, o.j = s.j = l, _(s, o)
|
||||
})(e, t)
|
||||
} catch (t) {
|
||||
le(t, e.S)
|
||||
}
|
||||
return null
|
||||
}, U = e => {
|
||||
const t = e.S,
|
||||
l = e.M;
|
||||
64 & e.t || (e.t |= 64, z(t), e.I(t), l || W()), e.T(t), e.O && (e.O(), e.O = void 0), 512 & e.t && ie((() => H(e, !1))), e.t &= -517
|
||||
}, W = () => {
|
||||
z(s.documentElement), ie((() => A(l, "appload", {
|
||||
detail: {
|
||||
namespace: "duet"
|
||||
}
|
||||
})))
|
||||
}, q = (e, t, l) => {
|
||||
if (e && e[t]) try {
|
||||
return e[t](l)
|
||||
} catch (e) {
|
||||
le(e)
|
||||
}
|
||||
}, V = (e, t) => e && e.then ? e.then(t) : t(), z = e => e.classList.add("hydrated"), B = (e, t, l, n, s, o, i) => {
|
||||
let c, r, u, a;
|
||||
if (1 === o.nodeType) {
|
||||
for (c = o.getAttribute("c-id"), c && (r = c.split("."), r[0] !== i && "0" !== r[0] || (u = {
|
||||
t: 0,
|
||||
A: r[0],
|
||||
F: r[1],
|
||||
H: r[2],
|
||||
L: r[3],
|
||||
v: o.tagName.toLowerCase(),
|
||||
j: o,
|
||||
p: null,
|
||||
m: null,
|
||||
g: null,
|
||||
k: null,
|
||||
h: null
|
||||
}, t.push(u), o.removeAttribute("c-id"), e.m || (e.m = []), e.m[u.L] = u, e = u, n && "0" === u.H && (n[u.L] = u.j))), a = o.childNodes.length - 1; a >= 0; a--) B(e, t, l, n, s, o.childNodes[a], i);
|
||||
if (o.shadowRoot)
|
||||
for (a = o.shadowRoot.childNodes.length - 1; a >= 0; a--) B(e, t, l, n, s, o.shadowRoot.childNodes[a], i)
|
||||
} else if (8 === o.nodeType) r = o.nodeValue.split("."), r[1] !== i && "0" !== r[1] || (c = r[0], u = {
|
||||
t: 0,
|
||||
A: r[1],
|
||||
F: r[2],
|
||||
H: r[3],
|
||||
L: r[4],
|
||||
j: o,
|
||||
p: null,
|
||||
m: null,
|
||||
g: null,
|
||||
k: null,
|
||||
v: null,
|
||||
h: null
|
||||
}, "t" === c ? (u.j = o.nextSibling, u.j && 3 === u.j.nodeType && (u.h = u.j.textContent, t.push(u), o.remove(), e.m || (e.m = []), e.m[u.L] = u, n && "0" === u.H && (n[u.L] = u.j))) : u.A === i && "s" === c && (u.v = "slot", o["s-sn"] = r[5] ? u.k = r[5] : "", o["s-sr"] = !0, l.push(u), e.m || (e.m = []), e.m[u.L] = u));
|
||||
else if (e && "style" === e.v) {
|
||||
const t = m(null, o.textContent);
|
||||
t.j = o, t.L = "0", e.m = [t]
|
||||
}
|
||||
}, D = (e, t) => {
|
||||
if (1 === e.nodeType) {
|
||||
let l = 0;
|
||||
for (; l < e.childNodes.length; l++) D(e.childNodes[l], t);
|
||||
if (e.shadowRoot)
|
||||
for (l = 0; l < e.shadowRoot.childNodes.length; l++) D(e.shadowRoot.childNodes[l], t)
|
||||
} else if (8 === e.nodeType) {
|
||||
const l = e.nodeValue.split(".");
|
||||
"o" === l[0] && (t.set(l[1] + "." + l[2], e), e.nodeValue = "", e["s-en"] = l[3])
|
||||
}
|
||||
}, G = (e, t, l) => {
|
||||
if (t.N) {
|
||||
e.watchers && (t.R = e.watchers);
|
||||
const n = Object.entries(t.N),
|
||||
s = e.prototype;
|
||||
if (n.map((([e, [n]]) => {
|
||||
31 & n || 2 & l && 32 & n ? Object.defineProperty(s, e, {
|
||||
get() {
|
||||
return ((e, t) => Y(this).U.get(t))(0, e)
|
||||
},
|
||||
set(l) {
|
||||
((e, t, l, n) => {
|
||||
const s = Y(e),
|
||||
o = s.S,
|
||||
i = s.U.get(t),
|
||||
c = s.t,
|
||||
r = s.i;
|
||||
if (l = ((e, t) => null == e || y(e) ? e : 4 & t ? "false" !== e && ("" === e || !!e) : 2 & t ? parseFloat(e) : 1 & t ? e + "" : e)(l, n.N[t][0]), !(8 & c && void 0 !== i || l === i) && (s.U.set(t, l), r)) {
|
||||
if (n.R && 128 & c) {
|
||||
const e = n.R[t];
|
||||
e && e.map((e => {
|
||||
try {
|
||||
r[e](l, i, t)
|
||||
} catch (e) {
|
||||
le(e, o)
|
||||
}
|
||||
}))
|
||||
}
|
||||
2 == (18 & c) && H(s, !1)
|
||||
}
|
||||
})(this, e, l, t)
|
||||
},
|
||||
configurable: !0,
|
||||
enumerable: !0
|
||||
}) : 1 & l && 64 & n && Object.defineProperty(s, e, {
|
||||
value(...t) {
|
||||
const l = Y(this);
|
||||
return l.W.then((() => l.i[e](...t)))
|
||||
}
|
||||
})
|
||||
})), 1 & l) {
|
||||
const l = new Map;
|
||||
s.attributeChangedCallback = function (e, t, n) {
|
||||
o.jmp((() => {
|
||||
const t = l.get(e);
|
||||
this[t] = (null !== n || "boolean" != typeof this[t]) && n
|
||||
}))
|
||||
}, e.observedAttributes = n.filter((([e, t]) => 15 & t[0])).map((([e, n]) => {
|
||||
const s = n[1] || e;
|
||||
return l.set(s, e), 512 & n[0] && t._.push([e, s]), s
|
||||
}))
|
||||
}
|
||||
}
|
||||
return e
|
||||
}, J = e => {
|
||||
q(e, "connectedCallback")
|
||||
}, K = e => {
|
||||
if (0 == (1 & o.t)) {
|
||||
const t = Y(e),
|
||||
l = t.C,
|
||||
n = () => {};
|
||||
if (1 & t.t) r(e, t, l.q), J(t.i);
|
||||
else {
|
||||
let n;
|
||||
t.t |= 1, n = e.getAttribute("s-id"), n && ((e, t, l, n) => {
|
||||
const i = e.shadowRoot,
|
||||
c = [],
|
||||
r = n.P = m(t, null);
|
||||
o.V || D(s.body, o.V = new Map), e["s-id"] = l, e.removeAttribute("s-id"), B(r, c, [], null, e, e, l), c.map((e => {
|
||||
const l = e.A + "." + e.F,
|
||||
n = o.V.get(l),
|
||||
s = e.j;
|
||||
n && "" === n["s-en"] && n.parentNode.insertBefore(s, n.nextSibling), i || (s["s-hn"] = t, n && (s["s-ol"] = n, s["s-ol"]["s-nr"] = s)), o.V.delete(l)
|
||||
}))
|
||||
})(e, l.$, n, t); {
|
||||
let l = e;
|
||||
for (; l = l.parentNode || l.host;)
|
||||
if (1 === l.nodeType && l.hasAttribute("s-id") && l["s-p"] || l["s-p"]) {
|
||||
F(t, t.M = l);
|
||||
break
|
||||
}
|
||||
}
|
||||
l.N && Object.entries(l.N).map((([t, [l]]) => {
|
||||
if (31 & l && e.hasOwnProperty(t)) {
|
||||
const l = e[t];
|
||||
delete e[t], e[t] = l
|
||||
}
|
||||
})), ie((() => (async (e, t, l, n, s) => {
|
||||
if (0 == (32 & t.t)) {
|
||||
{
|
||||
if (t.t |= 32, (s = se(l)).then) {
|
||||
const e = () => {};
|
||||
s = await s, e()
|
||||
}
|
||||
s.isProxied || (l.R = s.watchers, G(s, l, 2), s.isProxied = !0);
|
||||
const e = () => {};
|
||||
t.t |= 8;
|
||||
try {
|
||||
new s(t)
|
||||
} catch (e) {
|
||||
le(e)
|
||||
}
|
||||
t.t &= -9, t.t |= 128, e(), J(t.i)
|
||||
}
|
||||
if (s.style) {
|
||||
let e = s.style;
|
||||
const t = d(l);
|
||||
if (!oe.has(t)) {
|
||||
const n = () => {};
|
||||
((e, t, l) => {
|
||||
let n = oe.get(e);
|
||||
c && l ? (n = n || new CSSStyleSheet, n.replace(t)) : n = t, oe.set(e, n)
|
||||
})(t, e, !!(1 & l.t)), n()
|
||||
}
|
||||
}
|
||||
}
|
||||
const o = t.M,
|
||||
i = () => H(t, !0);
|
||||
o && o["s-rc"] ? o["s-rc"].push(i) : i()
|
||||
})(0, t, l)))
|
||||
}
|
||||
n()
|
||||
}
|
||||
}, Q = (e, t = {}) => {
|
||||
const n = [],
|
||||
i = t.exclude || [],
|
||||
c = l.customElements,
|
||||
r = s.head,
|
||||
u = r.querySelector("meta[charset]"),
|
||||
a = s.createElement("style"),
|
||||
f = [];
|
||||
let $, d = !0;
|
||||
Object.assign(o, t), o.l = new URL(t.resourcesUrl || "./", s.baseURI).href, o.t |= 2, e.map((e => e[1].map((t => {
|
||||
const l = {
|
||||
t: t[0],
|
||||
$: t[1],
|
||||
N: t[2],
|
||||
q: t[3]
|
||||
};
|
||||
l.N = t[2], l.q = t[3], l._ = [], l.R = {};
|
||||
const s = l.$,
|
||||
r = class extends HTMLElement {
|
||||
constructor(e) {
|
||||
super(e), ee(e = this, l)
|
||||
}
|
||||
connectedCallback() {
|
||||
$ && (clearTimeout($), $ = null), d ? f.push(this) : o.jmp((() => K(this)))
|
||||
}
|
||||
disconnectedCallback() {
|
||||
o.jmp((() => (() => {
|
||||
if (0 == (1 & o.t)) {
|
||||
const e = Y(this);
|
||||
e.o && (e.o.map((e => e())), e.o = void 0)
|
||||
}
|
||||
})()))
|
||||
}
|
||||
componentOnReady() {
|
||||
return Y(this).B
|
||||
}
|
||||
};
|
||||
l.D = e[0], i.includes(s) || c.get(s) || (n.push(s), c.define(s, G(r, l, 1)))
|
||||
})))), a.innerHTML = n + "{visibility:hidden}.hydrated{visibility:inherit}", a.setAttribute("data-styles", ""), r.insertBefore(a, u ? u.nextSibling : r.firstChild), d = !1, f.length ? f.map((e => e.connectedCallback())) : o.jmp((() => $ = setTimeout(W, 30)))
|
||||
}, X = new WeakMap, Y = e => X.get(e), Z = (e, t) => X.set(t.i = e, t), ee = (e, t) => {
|
||||
const l = {
|
||||
t: 0,
|
||||
S: e,
|
||||
C: t,
|
||||
U: new Map
|
||||
};
|
||||
return l.W = new Promise((e => l.T = e)), l.B = new Promise((e => l.I = e)), e["s-p"] = [], e["s-rc"] = [], r(e, l, t.q), X.set(e, l)
|
||||
}, te = (e, t) => t in e, le = (e, t) => (0, console.error)(e, t), ne = new Map, se = e => {
|
||||
const t = e.$.replace(/-/g, "_"),
|
||||
l = e.D,
|
||||
n = ne.get(l);
|
||||
return n ? n[t] : __sc_import_duet(`./${l}.entry.js`).then((e => (ne.set(l, e), e[t])), le)
|
||||
}, oe = new Map, ie = e => i().then(e);
|
||||
export {
|
||||
n as C, w as H, e as N, i as a, Q as b, T as c, s as d, I as g, p as h, o as p, Z as r, l as w
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
System.register([],(function(){"use strict";return{execute:function(){}}}));
|
||||
@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="/datapicker/default.css">
|
||||
<script src="/datapicker/jquery-3.5.0.js"></script>
|
||||
<script type="module" src="/datapicker/duet.esm.js"></script>
|
||||
<script nomodule src="/datapicker/duet.js"></script>
|
||||
<script src="/datapicker/duet.system.js"></script>
|
||||
|
||||
<style>
|
||||
.date_wrap {
|
||||
width: 280px;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="date_wrap">
|
||||
<duet-date-picker identifier="date" class="startDate"></duet-date-picker>
|
||||
</div>
|
||||
|
||||
<div class="date_wrap">
|
||||
<duet-date-picker identifier="date" class="endDate"></duet-date-picker>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const startDate = document.querySelector(".startDate");
|
||||
const endDate = document.querySelector(".endDate");
|
||||
|
||||
startDate.localization = {
|
||||
buttonLabel: 'Choose date',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
selectedDateMessage: 'Selected date is',
|
||||
prevMonthLabel: '이전 달 보기',
|
||||
nextMonthLabel: '다음 달 보기',
|
||||
monthSelectLabel: '월',
|
||||
yearSelectLabel: '년',
|
||||
closeLabel: 'Close window',
|
||||
calendarHeading: 'Choose a date',
|
||||
dayNames: ['일', '월', '화', '수', '목', '금', '토'],
|
||||
monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
}
|
||||
endDate.localization = {
|
||||
buttonLabel: 'Choose date',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
selectedDateMessage: 'Selected date is',
|
||||
prevMonthLabel: '이전 달 보기',
|
||||
nextMonthLabel: '다음 달 보기',
|
||||
monthSelectLabel: '월',
|
||||
yearSelectLabel: '년',
|
||||
closeLabel: 'Close window',
|
||||
calendarHeading: 'Choose a date',
|
||||
dayNames: ['일', '월', '화', '수', '목', '금', '토'],
|
||||
monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
custom-element
|
||||
dist
|
||||
www
|
||||
|
||||
hydrate/index.js
|
||||
hydrate/index.d.ts
|
||||
@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@ -0,0 +1,26 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on Node.js 12 running on macOS-latest
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build project
|
||||
run: npm run build
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Run hydrate tests
|
||||
run: cd hydrate/tests && npm install && npm test
|
||||
33
src/main/webapp/kofair_case_seed/script/plugin/date-picker-master/date-picker-master/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
node_modules
|
||||
**/node_modules/*
|
||||
lerna-debug.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
.idea
|
||||
.eslintcache
|
||||
.DS_Store
|
||||
.vscode
|
||||
.stencil
|
||||
.stats
|
||||
package-lock.json
|
||||
www
|
||||
__diff_output__
|
||||
src/components.d.ts
|
||||
dist/*
|
||||
hydrate/*
|
||||
custom-element/*
|
||||
|
||||
!hydrate/tests/test.js
|
||||
!hydrate/tests/setup.js
|
||||
!hydrate/tests/package.js
|
||||
!hydrate/tests/.npmrc
|
||||
!hydrate/tests/fixtures
|
||||
!hydrate/tests/__image_snapshots__
|
||||
!hydrate/tests/.gitignore
|
||||
!hydrate/package.json
|
||||
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.vsix
|
||||
@ -0,0 +1 @@
|
||||
package-lock=false
|
||||
@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
custom-element
|
||||
dist
|
||||
www
|
||||
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
*.md
|
||||
|
||||
src/components.d.ts
|
||||
@ -0,0 +1,14 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"semi": false,
|
||||
"requirePragma": false,
|
||||
"insertPragma": false,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"arrowParens": "avoid",
|
||||
"proseWrap": "preserve"
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
{
|
||||
"files": {
|
||||
"include": "./src/components/duet-date-picker/*.scss",
|
||||
"ignore": ["dist/**/*", "www/**/*", "node_modules/**"]
|
||||
},
|
||||
"rules": {
|
||||
"property-sort-order": [
|
||||
1,
|
||||
{
|
||||
"order": "alphabetical"
|
||||
}
|
||||
],
|
||||
"class-name-format": [
|
||||
1,
|
||||
{
|
||||
"convention": "strictbem"
|
||||
}
|
||||
],
|
||||
"no-vendor-prefixes": 0,
|
||||
"no-color-literals": 0,
|
||||
"nesting-depth": [
|
||||
1,
|
||||
{
|
||||
"max-depth": 2
|
||||
}
|
||||
],
|
||||
"no-qualifying-elements": [
|
||||
1,
|
||||
{
|
||||
"allow-element-with-attribute": true
|
||||
}
|
||||
],
|
||||
"force-pseudo-nesting": 0,
|
||||
"mixins-before-declarations": 1,
|
||||
"leading-zero": 0,
|
||||
"quotes": [
|
||||
1,
|
||||
{
|
||||
"style": "double"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at duetdesignsystem@lahitapiola.fi. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 LocalTapiola Services Ltd / Duet Design System
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -0,0 +1,728 @@
|
||||
 [](https://www.npmjs.com/package/@duetds/date-picker)  [](https://github.com/prettier/prettier)
|
||||
|
||||
# Duet Date Picker
|
||||
|
||||
Duet Date Picker is an open source version of Duet Design System’s [accessible date picker](https://www.duetds.com/components/date-picker/). Duet Date Picker can be implemented and used across any JavaScript framework or no framework at all. We accomplish this by using standardized web platform APIs and Web Components.
|
||||
|
||||
Why yet another date picker? Our team working on [Duet Design System](https://www.duetds.com/) couldn’t find an existing date picker that would’ve ticked all the requirements we had for accessibility _(supporting WCAG 2.1 as well as we can),_ so we decided to build one and open source it so that others could benefit from this work as well.
|
||||
|
||||
Duet Date Picker comes with built-in functionality that allows you to set a minimum and a maximum allowed date. These settings can be combined or used alone, depending on the need. Please note that the date values must be passed in IS0-8601 format: `YYYY-MM-DD`.
|
||||
|
||||
**[Read getting started instructions ›](#getting-started)**
|
||||
<br/>
|
||||
**[Learn more about Duet ›](https://www.duetds.com)**
|
||||
|
||||

|
||||
|
||||
## Live demo
|
||||
|
||||
- [https://duetds.github.io/date-picker/](https://duetds.github.io/date-picker/)
|
||||
|
||||
## Features
|
||||
|
||||
- Can be used with any JavaScript framework.
|
||||
- No external dependencies.
|
||||
- Weighs only ~10kb minified and Gzip’ed (this includes all styles and icons).
|
||||
- Built with accessibility in mind.
|
||||
- Supports all modern browsers and screen readers.
|
||||
- Additionally, limited support offered for IE11 and Edge 17+.
|
||||
- Allows theming using CSS Custom Properties.
|
||||
- Support for localization.
|
||||
- Customizable date parsing and formatting.
|
||||
- Support for changing the first day of the week.
|
||||
- Comes with modified interface for mobile devices to provide better user experience.
|
||||
- Supports touch gestures for changing months and closing the picker.
|
||||
- Built using [Stencil.js](https://stenciljs.com/) and Web Components.
|
||||
- Free to use under the MIT license.
|
||||
|
||||
## Browser support
|
||||
|
||||
- Google Chrome 61+
|
||||
- Apple Safari 10+
|
||||
- Firefox 63+
|
||||
- Microsoft Edge 17+
|
||||
- Opera 63+
|
||||
- Samsung Browser 8.2+
|
||||
- Internet Explorer 11
|
||||
|
||||
## Screen Reader support
|
||||
|
||||
We offer support for the following screen readers. For more information about the level of support and possible issues with the implementation, please refer to the included [accessibility audit](https://github.com/duetds/date-picker/blob/master/accessibility-audit.pdf).
|
||||
|
||||
- VoiceOver on macOS and iOS
|
||||
- TalkBack on Android
|
||||
- NVDA on Windows
|
||||
- Jaws on Windows
|
||||
|
||||
## Keyboard support
|
||||
|
||||
Duet Date Picker’s keyboard support is built to closely follow [W3C Date Picker Dialog example](https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/datepicker-dialog.html) with some small exceptions to e.g. better support iOS VoiceOver and Android TalkBack.
|
||||
|
||||
### Choose date button
|
||||
|
||||
- `Space, Enter`: Opens the date picker dialog and moves focus to the first select menu in the dialog.
|
||||
|
||||
### Date picker dialog
|
||||
|
||||
- `Esc`: Closes the date picker dialog and moves focus back to the “choose date” button.
|
||||
- `Tab`: Moves focus to the next element in the dialog. Please note since the calendar uses `role="grid"`, only one button in the calendar grid is in the tab sequence. Additionally, if focus is on the last focusable element, focus is next moved back to the first focusable element inside the date picker dialog.
|
||||
- `Shift + Tab`: Same as above, but in reverse order.
|
||||
|
||||
### Date picker dialog: Month/year buttons
|
||||
|
||||
- `Space, Enter`: Changes the month and/or year displayed.
|
||||
|
||||
### Date picker dialog: Date grid
|
||||
|
||||
- `Space, Enter`: Selects a date, closes the dialog, and moves focus back to the “Choose Date” button. Additionally updates the value of the Duet Date Picker input with the selected date, and adds selected date to “Choose Date” button label.
|
||||
- `Arrow up`: Moves focus to the same day of the previous week.
|
||||
- `Arrow down`: Moves focus to the same day of the next week.
|
||||
- `Arrow right`: Moves focus to the next day.
|
||||
- `Arrow left`: Moves focus to the previous day.
|
||||
- `Home`: Moves focus to the first day (e.g Monday) of the current week.
|
||||
- `End`: Moves focus to the last day (e.g. Sunday) of the current week.
|
||||
- `Page Up`: Changes the grid of dates to the previous month and sets focus on the same day of the same week.
|
||||
- `Shift + Page Up`: Changes the grid of dates to the previous year and sets focus on the same day of the same week.
|
||||
- `Page Down`: Changes the grid of dates to the next month and sets focus on the same day of the same week.
|
||||
- `Shift + Page Down`: Changes the grid of dates to the next year and sets focus on the same day of the same week.
|
||||
|
||||
### Date picker dialog: Close button
|
||||
|
||||
- `Space, Enter`: Closes the dialog, moves focus to “choose date” button, but does not update the date in input.
|
||||
|
||||
## Getting started
|
||||
|
||||
Integrating Duet Date Picker to a project without a JavaScript framework is very straight forward. If you’re working on a simple HTML page, you can start using Duet Date Picker immediately by adding these tags to the `<head>`:
|
||||
|
||||
```html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/duet.esm.js"></script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/duet.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/themes/default.css" />
|
||||
```
|
||||
|
||||
Once included, Duet Date Picker can be used in your markup like any other regular HTML element:
|
||||
|
||||
```html
|
||||
<label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
```
|
||||
|
||||
**Please note: Importing the CSS file is optional and only needed if you’re planning on using the default theme. See [theming section](#theming) for more information. Additionally, while the above method is the easiest and fastest way to get started, you can also install Duet Date Picker via NPM. Scroll down for the [installation instructions](#installation).**
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ---------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- |
|
||||
| `dateAdapter` | -- | Date adapter, for custom parsing/formatting. Must be object with a `parse` function which accepts a `string` and returns a `Date`, and a `format` function which accepts a `Date` and returns a `string`. Default is IS0-8601 parsing and formatting. | `DuetDateAdapter` | `isoAdapter` |
|
||||
| `direction` | `direction` | Forces the opening direction of the calendar modal to be always left or right. This setting can be useful when the input is smaller than the opening date picker would be as by default the picker always opens towards right. | `"left" \| "right"` | `"right"` |
|
||||
| `disabled` | `disabled` | Makes the date picker input component disabled. This prevents users from being able to interact with the input, and conveys its inactive state to assistive technologies. | `boolean` | `false` |
|
||||
| `firstDayOfWeek` | `first-day-of-week` | Which day is considered first day of the week? `0` for Sunday, `1` for Monday, etc. Default is Monday. | `DaysOfWeek.Friday \| DaysOfWeek.Monday \| DaysOfWeek.Saturday \| DaysOfWeek.Sunday \| DaysOfWeek.Thursday \| DaysOfWeek.Tuesday \| DaysOfWeek.Wednesday` | `DaysOfWeek.Monday` |
|
||||
| `identifier` | `identifier` | Adds a unique identifier for the date picker input. Use this instead of html `id` attribute. | `string` | `""` |
|
||||
| `localization` | -- | Button labels, day names, month names, etc, used for localization. Default is English. | `{ buttonLabel: string; placeholder: string; selectedDateMessage: string; prevMonthLabel: string; nextMonthLabel: string; monthSelectLabel: string; yearSelectLabel: string; closeLabel: string; calendarHeading: string; dayNames: DayNames; monthNames: MonthsNames; monthNamesShort: MonthsNames; }` | `defaultLocalization` |
|
||||
| `max` | `max` | Maximum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD. This setting can be used alone or together with the min property. | `string` | `""` |
|
||||
| `min` | `min` | Minimum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD. This setting can be used alone or together with the max property. | `string` | `""` |
|
||||
| `name` | `name` | Name of the date picker input. | `string` | `"date"` |
|
||||
| `role` | `role` | Defines a specific role attribute for the date picker input. | `string` | `undefined` |
|
||||
| `required` | `required` | Should the input be marked as required? | `boolean` | `false` |
|
||||
| `value` | `value` | Date value. Must be in IS0-8601 format: YYYY-MM-DD. | `string` | `""` |
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Description | Type |
|
||||
| ------------ | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `duetBlur` | Event emitted the date picker input is blurred. | `CustomEvent<{ component: "duet-date-picker"; }>` |
|
||||
| `duetChange` | Event emitted when a date is selected. | `CustomEvent<{ component: "duet-date-picker"; valueAsDate: Date; value: string; }>` |
|
||||
| `duetFocus` | Event emitted the date picker input is focused. | `CustomEvent<{ component: "duet-date-picker"; }>` |
|
||||
| `duetOpen` | Event emitted when the date picker modal is opened. | `CustomEvent<{ component: "duet-date-picker"; }>` |
|
||||
| `duetClose` | Event emitted the date picker modal is closed. | `CustomEvent<{ component: "duet-date-picker"; }>` |
|
||||
|
||||
## Methods
|
||||
|
||||
### `hide(moveFocusToButton?: boolean) => Promise<void>`
|
||||
|
||||
Hide the calendar modal. Set `moveFocusToButton` to false to prevent focus
|
||||
returning to the date picker's button. Default is true.
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `Promise<void>`
|
||||
|
||||
### `setFocus() => Promise<void>`
|
||||
|
||||
Sets focus on the date picker's input. Use this method instead of the global `focus()`.
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `Promise<void>`
|
||||
|
||||
### `show() => Promise<void>`
|
||||
|
||||
Show the calendar modal, moving focus to the calendar inside.
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `Promise<void>`
|
||||
|
||||
## Installation
|
||||
|
||||
Before moving further, please make sure you have [Node.js](https://nodejs.org/en/) installed on your machine. You can install the latest version through [their website](https://nodejs.org/en/). If you’re planning on using Duet Date Picker in a project that doesn’t yet use [Node Package Manager](https://www.npmjs.com), you’ll have to first create a [package.json](https://docs.npmjs.com/files/package.json) file. To do so, run <code>npm init</code> and follow the steps provided.
|
||||
|
||||
Once finished, you can install Duet Date Picker by running:
|
||||
|
||||
```shell
|
||||
# WEB COMPONENT for HTML, Ember, Vue.js, React, Angular and Vanilla JS:
|
||||
npm install @duetds/date-picker
|
||||
```
|
||||
|
||||
## Usage with basic HTML
|
||||
|
||||
**Please note: We recommend the usage of CDN like JSDelivr over the below approach if you’re not [server side rendering](#server-side-rendering) Duet Date Picker. See [getting started section](#getting-started) to find the correct script tags.**
|
||||
|
||||
Once you’ve installed `@duetds/date-picker` package into your project, it’s recommended to create a copy task that copies Duet Date Picker component from `node_modules` to a location you’ve specified. One such tool that can do this is [NCP](https://www.npmjs.com/package/ncp). You can install `ncp` by running:
|
||||
|
||||
```shell
|
||||
npm install ncp --save-dev
|
||||
```
|
||||
|
||||
Once installed, add a script to your package.json that copies the component library from Duet’s package into a location you’ve specified:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"copy:duet-date-picker": "ncp node_modules/@duetds/date-picker/dist src/SPECIFY_PATH"
|
||||
}
|
||||
```
|
||||
|
||||
You can call this script while starting up your app to make sure you’ve always got the latest code copied over. If you’re using an UNIX-like environment, you can use `&` as the separator:
|
||||
|
||||
```json
|
||||
"start": "copy:duet-date-picker & dev"
|
||||
```
|
||||
|
||||
Otherwise, if you need a cross-platform solution, use [npm-run-all module](https://www.npmjs.com/package/npm-run-all):
|
||||
|
||||
```json
|
||||
"start": "npm-run-all copy:duet-date-picker dev"
|
||||
```
|
||||
|
||||
Once you have a copy task in place and have copied Duet Date Picker over, you can put tags similar to these in the `<head>` of your `index.html` (importing the CSS file is optional and only needed if you’re planning on using the default theme. See [theming section](#theming) for more information):
|
||||
|
||||
```html
|
||||
<script type="module" src="SPECIFY_YOUR_PATH/duet.esm.js"></script>
|
||||
<script nomodule src="SPECIFY_YOUR_PATH/duet.js"></script>
|
||||
<link rel="stylesheet" href="SPECIFY_YOUR_PATH/duet.css" />
|
||||
```
|
||||
|
||||
Once included, Duet Date Picker can be used in your basic HTML markup as in the following example:
|
||||
|
||||
```html
|
||||
<label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
```
|
||||
|
||||
To know when this tag name becomes defined, you can use `window.customElements.whenDefined()`. It returns a Promise that resolves when the element becomes defined:
|
||||
|
||||
```js
|
||||
customElements.whenDefined("duet-date-picker").then(() => {
|
||||
document.querySelector("duet-date-picker").show()
|
||||
});
|
||||
```
|
||||
|
||||
## Usage with Angular
|
||||
|
||||
Before you can use Duet Date Picker in Angular, you must import and add Angular’s `CUSTOM_ELEMENTS_SCHEMA`. This allows the use of Web Components in HTML markup, without the compiler producing errors. The `CUSTOM_ELEMENTS_SCHEMA` needs to be included in any module that uses custom elements. Typically, this can be added to `AppModule`:
|
||||
|
||||
```js
|
||||
// ...
|
||||
// Import custom elements schema
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
||||
|
||||
@NgModule({
|
||||
// ...
|
||||
// Add custom elements schema to NgModule
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class AppModule { }
|
||||
```
|
||||
|
||||
The final step is to load and register Duet Date Picker in the browser. `@duetds/date-picker` includes a main function that handles this. That function is called `defineCustomElements()` and it needs to be called once during the bootstrapping of your application. One convenient place to do this is in `main.ts` as such:
|
||||
|
||||
```js
|
||||
// Import Duet Date Picker
|
||||
import { defineCustomElements } from "@duetds/date-picker/dist/loader";
|
||||
// ...
|
||||
// Register Duet Date Picker
|
||||
defineCustomElements(window);
|
||||
```
|
||||
|
||||
Once included, Duet Date Picker can be used in your HTML markup as in the following example:
|
||||
|
||||
```html
|
||||
<label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
```
|
||||
|
||||
Please note that you need to also import `duet.css` separately if you want to use the default theme. See [theming section](#theming) for more information.
|
||||
|
||||
### Accessing using ViewChild and ViewChildren
|
||||
|
||||
Once included, components could also be referenced in your code using `ViewChild` and `ViewChildren` as shown in the [Stencil.js documentation](https://stenciljs.com/docs/angular).
|
||||
|
||||
## Usage with Vue.js
|
||||
|
||||
To integrate `@duetds/date-picker` into a [Vue.js application](https://vuejs.org/), edit `src/main.js` to include:
|
||||
|
||||
```js
|
||||
// Import Duet Date Picker
|
||||
import { defineCustomElements } from "@duetds/date-picker/dist/loader";
|
||||
|
||||
// ...
|
||||
// configure Vue.js to ignore Duet Date Picker
|
||||
Vue.config.ignoredElements = [/duet-\w*/];
|
||||
|
||||
// Register Duet Date Picker
|
||||
defineCustomElements(window);
|
||||
|
||||
new Vue({
|
||||
render: h => h(App)
|
||||
}).$mount("#app");
|
||||
```
|
||||
|
||||
Once included, Duet Date Picker can be used in your HTML markup as in the following example:
|
||||
|
||||
```html
|
||||
<template>
|
||||
<label for="date">Choose a date</label>
|
||||
<duet-date-picker
|
||||
identifier="date"
|
||||
:localization.prop="localisation_uk">
|
||||
</duet-date-picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const localisation_uk = {
|
||||
buttonLabel: 'Choose date',
|
||||
placeholder: 'DD/MM/YYYY',
|
||||
selectedDateMessage: 'Selected date is',
|
||||
prevMonthLabel: 'Previous month',
|
||||
nextMonthLabel: 'Next month',
|
||||
monthSelectLabel: 'Month',
|
||||
yearSelectLabel: 'Year',
|
||||
closeLabel: 'Close window',
|
||||
calendarHeading: 'Choose a date',
|
||||
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Please note that you need to also import `duet.css` separately if you want to use the default theme. See [theming section](#theming) for more information.
|
||||
|
||||
Please also note that in order to use duet-date-picker's own custom properties (as seen on the properties section), vue must recognise that such options are being passed down as properties rather than attributes, hence the `.prop` at the end.
|
||||
|
||||
## Usage with React
|
||||
|
||||
With an application built using the `create-react-app` script the easiest way to include Duet Date Picker is to call `defineCustomElements(window)` from the `index.js` file:
|
||||
|
||||
```js
|
||||
// Import Duet Date Picker
|
||||
import { defineCustomElements } from "@duetds/date-picker/dist/loader";
|
||||
|
||||
// ...
|
||||
// Register Duet Date Picker
|
||||
defineCustomElements(window);
|
||||
```
|
||||
|
||||
Then you can create a thin React wrapper component to handle listening for events, cleanup, passing properties etc:
|
||||
|
||||
```js
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
function useListener(ref, eventName, handler) {
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
const element = ref.current;
|
||||
element.addEventListener(eventName, handler)
|
||||
return () => element.removeEventListener(eventName, handler)
|
||||
}
|
||||
}, [eventName, handler, ref])
|
||||
}
|
||||
|
||||
export function DatePicker({
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onOpen,
|
||||
onClose,
|
||||
dateAdapter,
|
||||
localization,
|
||||
...props
|
||||
}) {
|
||||
const ref = useRef(null)
|
||||
|
||||
useListener(ref, "duetChange", onChange)
|
||||
useListener(ref, "duetFocus", onFocus)
|
||||
useListener(ref, "duetBlur", onBlur)
|
||||
useListener(ref, "duetOpen", onOpen)
|
||||
useListener(ref, "duetClose", onClose)
|
||||
|
||||
useEffect(() => {
|
||||
ref.current.localization = localization
|
||||
ref.current.dateAdapter = dateAdapter
|
||||
}, [localization, dateAdapter])
|
||||
|
||||
return <duet-date-picker ref={ref} {...props}></duet-date-picker>
|
||||
}
|
||||
```
|
||||
|
||||
Then the wrapper can be used like any other React component:
|
||||
|
||||
```js
|
||||
<DatePicker
|
||||
value="2020-08-24"
|
||||
onChange={e => console.log(e.detail)}
|
||||
/>
|
||||
```
|
||||
|
||||
Please note that you need to also import `duet.css` separately if you want to use the default theme. See [theming section](#theming) for more information.
|
||||
|
||||
## Usage with Ember
|
||||
|
||||
Duet Date Picker can be easily integrated into Ember thanks to the `ember-cli-stencil` addon that handles:
|
||||
|
||||
- Importing the required files into your `vendor.js`
|
||||
- Copying the component definitions into your `assets` directory
|
||||
- Optionally generating a wrapper component for improved compatibility with older Ember versions
|
||||
|
||||
Start by installing the Ember addon:
|
||||
|
||||
```shell
|
||||
ember install ember-cli-stencil ember-auto-import
|
||||
```
|
||||
|
||||
When you build your application, Stencil collections in your dependencies will be automatically discovered and pulled into your application. You might get a ```Can't resolve``` error when building. The easiest way to resolve that issue is by adding an alias to your ```ember-cli-build.js``` file.
|
||||
|
||||
```js
|
||||
autoImport: {
|
||||
alias: {
|
||||
'@duetds/date-picker/loader': '@duetds/date-picker/dist/loader/index.cjs',
|
||||
},
|
||||
},
|
||||
```
|
||||
For more information, see [ember-cli-stencil documentation](https://github.com/alexlafroscia/ember-cli-stencil).
|
||||
|
||||
Ember octane example:
|
||||
|
||||
```html
|
||||
<label for="date">Choose a date.</label>
|
||||
<duet-date-picker identifier="date" {{prop localization=this.localization}} ></duet-date-picker>
|
||||
```
|
||||
|
||||
```js
|
||||
import Controller from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
export default class ExampleController extends Controller {
|
||||
@tracked localization = {
|
||||
buttonLabel: "Choose date",
|
||||
placeholder: "mm/dd/yyyy",
|
||||
selectedDateMessage: "Selected date is",
|
||||
prevMonthLabel: "Previous month",
|
||||
nextMonthLabel: "Next month",
|
||||
monthSelectLabel: "Month",
|
||||
yearSelectLabel: "Year",
|
||||
closeLabel: "Close window",
|
||||
calendarHeading: "Choose a date",
|
||||
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
||||
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## IE11 and Edge 17/18 polyfills
|
||||
|
||||
If you want the Duet Date Picker custom element to work on older browser, you need to add the `applyPolyfills()` that surround the `defineCustomElements()` function:
|
||||
|
||||
```js
|
||||
import { applyPolyfills, defineCustomElements } from "@duetds/date-picker/lib/loader";
|
||||
// ...
|
||||
applyPolyfills().then(() => {
|
||||
defineCustomElements(window)
|
||||
})
|
||||
```
|
||||
|
||||
## Using events
|
||||
|
||||
We encourage the use of DOM events, but additionally provide custom events to make handling of certain event types easier. All custom events are documented in this same readme [under the “Events” heading](#events).
|
||||
|
||||
Duet Date Picker provides e.g. a custom event called `duetChange`. This custom event includes an object called `detail` which includes for example the selected date:
|
||||
|
||||
```js
|
||||
// Select the date picker component
|
||||
const date = document.querySelector("duet-date-picker")
|
||||
|
||||
// Listen for when date is selected
|
||||
date.addEventListener("duetChange", function(e) {
|
||||
console.log("selected date", e.detail.valueAsDate)
|
||||
})
|
||||
```
|
||||
|
||||
The console output for the above code looks like this:
|
||||
|
||||
```shell
|
||||
selected date Sat Aug 15 2020 00:00:00 GMT+0300 (Eastern European Summer Time)
|
||||
```
|
||||
|
||||
## Theming
|
||||
|
||||
Duet Date Picker uses CSS Custom Properties to make it easy to theme the picker. The component ships with a default theme that you can import either from the NPM package or directly from a CDN like JSDelivr:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/themes/default.css" />
|
||||
```
|
||||
|
||||
The above CSS file provides the following Custom Properties that you can override with your own properties:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--duet-color-primary: #005fcc;
|
||||
--duet-color-text: #333;
|
||||
--duet-color-text-active: #fff;
|
||||
--duet-color-placeholder: #666;
|
||||
--duet-color-button: #f5f5f5;
|
||||
--duet-color-surface: #fff;
|
||||
--duet-color-overlay: rgba(0, 0, 0, 0.8);
|
||||
--duet-color-border: #333;
|
||||
|
||||
--duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--duet-font-normal: 400;
|
||||
--duet-font-bold: 600;
|
||||
|
||||
--duet-radius: 4px;
|
||||
--duet-z-index: 600;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
If you wish to customize any of the default properties shown above, *we recommend to NOT import or link to the provided CSS,* but instead copying the above code into your own stylesheet and replacing the values used there.
|
||||
|
||||
Additionally, you’re able to override Duet Date Picker’s default styles by using e.g. `.duet-date__input` selector in your own stylesheet. This allows you to give the form input and e.g. date picker toggle button a visual look that matches the rest of your website.
|
||||
|
||||
## Localization
|
||||
|
||||
Duet Date Picker offers full support for localization. This includes the text labels and date formats used. Below is an example of a date picker that is using Finnish date format and localization.
|
||||
|
||||
```html
|
||||
<label for="date">Valitse päivämäärä</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
const picker = document.querySelector("duet-date-picker")
|
||||
const DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
|
||||
|
||||
picker.dateAdapter = {
|
||||
parse(value = "", createDate) {
|
||||
const matches = value.match(DATE_FORMAT)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format(date) {
|
||||
return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`
|
||||
},
|
||||
}
|
||||
|
||||
picker.localization = {
|
||||
buttonLabel: "Valitse päivämäärä",
|
||||
placeholder: "pp.kk.vvvv",
|
||||
selectedDateMessage: "Valittu päivämäärä on",
|
||||
prevMonthLabel: "Edellinen kuukausi",
|
||||
nextMonthLabel: "Seuraava kuukausi",
|
||||
monthSelectLabel: "Kuukausi",
|
||||
yearSelectLabel: "Vuosi",
|
||||
closeLabel: "Sulje ikkuna",
|
||||
calendarHeading: "Valitse päivämäärä",
|
||||
dayNames: [
|
||||
"Sunnuntai", "Maanantai", "Tiistai", "Keskiviikko",
|
||||
"Torstai", "Perjantai", "Lauantai"
|
||||
],
|
||||
monthNames: [
|
||||
"Tammikuu", "Helmikuu", "Maaliskuu", "Huhtikuu",
|
||||
"Toukokuu", "Kesäkuu", "Heinäkuu", "Elokuu",
|
||||
"Syyskuu", "Lokakuu", "Marraskuu", "Joulukuu"
|
||||
],
|
||||
monthNamesShort: [
|
||||
"Tammi", "Helmi", "Maalis", "Huhti", "Touko", "Kesä",
|
||||
"Heinä", "Elo", "Syys", "Loka", "Marras", "Joulu"
|
||||
],
|
||||
locale: "fi-FI",
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Please note that you must provide the entirety of the localization properties in the object when overriding with your custom localization.
|
||||
|
||||
## Control which days are selectable
|
||||
|
||||
Duet Date Picker allows you to disable the selection of specific days. Below is an example of a date picker that is disabling weekends.
|
||||
|
||||
Be aware, this only disables selection of dates in the popup calendar. You must still handle the case where a user manually enters a disallowed date into the input.
|
||||
|
||||
```html
|
||||
<label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
function isWeekend(date) {
|
||||
return date.getDay() === 0 || date.getDay() === 6
|
||||
}
|
||||
|
||||
const pickerDisableWeekend = document.querySelector("duet-date-picker")
|
||||
pickerDisableWeekend.isDateDisabled = isWeekend
|
||||
|
||||
pickerDisableWeekend.addEventListener("duetChange", function(e) {
|
||||
if (isWeekend(e.detail.valueAsDate)) {
|
||||
alert("Please select a weekday")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Server side rendering
|
||||
|
||||
Duet Date Picker package includes a hydrate app that is a bundle of the same components, but compiled so that they can be hydrated on a NodeJS server and generate static HTML and CSS. To get started, import the hydrate app into your server’s code like so:
|
||||
|
||||
```js
|
||||
import hydrate from "@duetds/date-picker/hydrate"
|
||||
```
|
||||
|
||||
If you are using for example [Eleventy](https://www.11ty.dev/), you could now add a transform into `.eleventy.js` configuration file that takes content as an input and processes it using Duet’s hydrate app:
|
||||
|
||||
```js
|
||||
eleventyConfig.addTransform("hydrate", async(content, outputPath) => {
|
||||
if (process.env.ELEVENTY_ENV == "production") {
|
||||
if (outputPath.endsWith(".html")) {
|
||||
try {
|
||||
const results = await hydrate.renderToString(content, {
|
||||
clientHydrateAnnotations: true,
|
||||
removeScripts: false,
|
||||
removeUnusedStyles: false
|
||||
})
|
||||
return results.html
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
}
|
||||
return content
|
||||
})
|
||||
```
|
||||
|
||||
The above transform gives you server side rendered components that function without JavaScript. Please note that you need to separately pre-render the content for each theme you want to support.
|
||||
|
||||
## Single file bundle
|
||||
|
||||
Duet Date Picker also offers a single file bundle without the polyfills and other additional functionality included in the default output. To import that instead of the default output, use:
|
||||
|
||||
```jsx
|
||||
import { DuetDatePicker } from "@duetds/date-picker/custom-element";
|
||||
|
||||
customElements.define("duet-date-picker", DuetDatePicker);
|
||||
```
|
||||
|
||||
Please note that this custom-element output does not automatically define the custom elements or apply any polyfills which is why we’re defining the custom element above ourselves.
|
||||
|
||||
For more details, please see [Stencil.js documentation](https://stenciljs.com/docs/custom-elements).
|
||||
|
||||
## Optimizing CDN performance
|
||||
|
||||
If you wish to make sure Duet Date Picker shows up as quickly as possible when loading the scripts from JSDelivr CDN, you can preload the key parts using link `rel="preload"`. To do this, add these tags in the `<head>` of your webpage before any other `<script>` or `<link>` tags:
|
||||
|
||||
```html
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/duet.esm.js" as="script" crossorigin="anonymous" />
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/duet-date-picker.entry.js" as="script" crossorigin="anonymous" />
|
||||
```
|
||||
|
||||
In case you’re also using one of the included themes, you can preload them the same way using the below tag:
|
||||
|
||||
```html
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/themes/default.css" as="style" />
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
### Development server
|
||||
|
||||
- Clone the repository by running `git clone git@github.com:duetds/duet-date-picker.git`.
|
||||
- Once cloned, open the directory and run `npm install`.
|
||||
- Run `npm start` to get the development server and watch tasks up and running. This will also automatically open a new browser window with an example page.
|
||||
- To edit the example page’s source, see `./src/index.html`.
|
||||
|
||||
### Testing and building
|
||||
|
||||
- To run the unit, end-to-end and visual diff tests use `npm run test`.
|
||||
- To build the project use `npm run build`.
|
||||
|
||||
### Publishing the package
|
||||
|
||||
_**NOTE:** this section is for maintainers and can be ignored by contributors._
|
||||
|
||||
The process for publishing a stable or a beta release differs.
|
||||
|
||||
To publish a new _stable_ release, use the `npm version` command. The npm docs for the [version command](https://docs.npmjs.com/cli/v6/commands/npm-version) explains in detail how this command works.
|
||||
|
||||
E.g. to publish a new minor version:
|
||||
|
||||
```sh
|
||||
npm version minor
|
||||
```
|
||||
|
||||
This will run tests, build the project, bump the minor version in package.json, commit and tag the changes, publish to npm, and finally push any commits to github.
|
||||
|
||||
To publish a new _beta_ release, do the following:
|
||||
|
||||
1. Bump version in `package.json` and elsewhere.
|
||||
1. Commit your changes.
|
||||
1. Tag new release by running `git tag -a 1.2.0-beta.0 -m "1.2.0-beta.0"`.
|
||||
1. Push your changes to Git and then run `npm publish --tag beta`.
|
||||
1. Push to git: `git push --tags --no-verify`.
|
||||
|
||||
## Changelog
|
||||
|
||||
- `1.4.0`:
|
||||
- Add support for disabling arbitrary dates in the calendar via `isDateDisabled` prop ([#52](https://github.com/duetds/date-picker/pull/52) and [#80](https://github.com/duetds/date-picker/pull/80)).
|
||||
- Disable month options in dropdown that would fall outside of min/max ([#82](https://github.com/duetds/date-picker/pull/80)).
|
||||
- Drop `keyboardInstruction` text - it is redundant since we now use a standard `<table>` element. ([#87](https://github.com/duetds/date-picker/pull/87)).
|
||||
- `1.3.0`:
|
||||
- Add new theme variable `--duet-border-color` for customising the input's border color. Falls back to previous value `--duet-color-text` if not set ([#70](https://github.com/duetds/date-picker/pull/70)).
|
||||
- Improve handling of disallowed characters so that cursor position is maintained.
|
||||
- Add new `duetOpen` and `duetClose` events to correspond with opening and closing the calendar ([#73](https://github.com/duetds/date-picker/pull/73)).
|
||||
- Fix click outside logic so that it works when nested in a shadow DOM ([#65](https://github.com/duetds/date-picker/pull/65)).
|
||||
- `1.2.0`:
|
||||
- Improvements to screen reader accessibility.
|
||||
- Ensure table can be navigated with table navigation commands.
|
||||
- Ensure column headers are announced out when navigating table columns.
|
||||
- Ensure month/year is announced whenever it changes.
|
||||
- Improve how dates are presented to screen readers. Use formats like "17 November 2020" instead of "2020-11-17".
|
||||
- Year dropdown now shows every year that satisfies min/max range.
|
||||
- `1.1.0`: Adds support for `required` attribute. Ensures date is always submitted in ISO format. Updates @stencil/core to 2.3.0.
|
||||
- `1.0.4`: Improves stability for NVDA + Chrome on Windows. Also fixes an issue which caused build attempts to fail due to snapshot mismatch.
|
||||
- `1.0.2`: Documentation improvements.
|
||||
- `1.0.1`: Hitting arrow keys on year select on Windows without first opening the dropdown previously causes odd results. This is now fixed.
|
||||
- `1.0.0`: Initial release.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Better examples on how to do date ranges, handle validation and so on.
|
||||
- Better theming and basic code examples.
|
||||
- Making it possible to pass in your own input component.
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2020 LocalTapiola Services Ltd / [Duet Design System](https://www.duetds.com).
|
||||
|
||||
Licensed under the [MIT license](https://github.com/duetds/date-picker/blob/master/LICENSE).
|
||||
@ -0,0 +1,573 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>Duet Date Picker</title>
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/duet.esm.js"
|
||||
as="script"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/duet-date-picker.entry.js"
|
||||
as="script"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link rel="preload" href="/date-picker/themes/default.css" as="style" />
|
||||
<link rel="preload" href="/date-picker/themes/dark.css" as="style" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/duet.esm.js"></script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/@duetds/date-picker@1.4.0/dist/duet/duet.js"></script>
|
||||
<link rel="stylesheet" href="/date-picker/themes/default.css" id="theme" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/styles/obsidian.min.css"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/highlight.min.js"></script>
|
||||
<script>
|
||||
document.documentElement.className += " js"
|
||||
if (hljs) {
|
||||
hljs.initHighlightingOnLoad()
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* ---------------------------------------------
|
||||
* BELOW STYLES ARE JUST FOR THE EXAMPLE,
|
||||
* YOU CAN REMOVE AND REPLACE THEM.
|
||||
* ------------------------------------------ */
|
||||
|
||||
body {
|
||||
font-weight: var(--duet-font-normal);
|
||||
font-family: var(--duet-font);
|
||||
color: var(--duet-color-text);
|
||||
background: var(--duet-color-surface);
|
||||
line-height: 1.5;
|
||||
margin: 0 auto;
|
||||
max-width: 40rem;
|
||||
font-size: 100%;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.js main {
|
||||
transition: opacity 300ms 80ms ease;
|
||||
visibility: hidden;
|
||||
will-change: opacity;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.js.hydrated main {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.js.hydrated .loader {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.js .loader,
|
||||
.js .loader:after {
|
||||
border-radius: 50%;
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
}
|
||||
|
||||
.js .loader {
|
||||
transition: opacity 300ms 80ms ease;
|
||||
top: calc(50% - 2.25rem);
|
||||
left: calc(50% - 2.25rem);
|
||||
font-size: 10px;
|
||||
position: fixed;
|
||||
text-indent: -9999rem;
|
||||
border-top: 0.5rem solid rgba(0, 0, 0, 0.1);
|
||||
border-right: 0.5rem solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 0.5rem solid rgba(0, 0, 0, 0.1);
|
||||
border-left: 0.5rem solid var(--duet-color-primary);
|
||||
transform: translateZ(0);
|
||||
animation: load 0.7s infinite linear;
|
||||
}
|
||||
@keyframes load {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: var(--duet-font-bold);
|
||||
font-size: 2rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: var(--duet-font-bold);
|
||||
font-size: 1.5rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--duet-color-primary);
|
||||
}
|
||||
|
||||
output {
|
||||
border-radius: var(--duet-radius);
|
||||
background: var(--duet-color-button);
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 0.6rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
pre {
|
||||
line-height: 1.5;
|
||||
font-size: 0.875rem;
|
||||
white-space: pre;
|
||||
display: block;
|
||||
}
|
||||
|
||||
pre code,
|
||||
code.hljs {
|
||||
border-radius: var(--duet-radius);
|
||||
font-weight: var(--duet-font-normal);
|
||||
background: #282b2e;
|
||||
color: #fff;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
span.hljs-name {
|
||||
font-weight: var(--duet-font-normal);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: monaco, Consolas, monospace, monospace;
|
||||
background: var(--duet-color-button);
|
||||
margin: 0;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.button,
|
||||
.theme {
|
||||
background: var(--duet-color-primary);
|
||||
color: var(--duet-color-text-active);
|
||||
border-radius: var(--duet-radius);
|
||||
font-weight: var(--duet-font-bold);
|
||||
font-size: 1rem;
|
||||
-webkit-appearance: none;
|
||||
padding: 0.75rem 1rem;
|
||||
margin: 0 0 0.5rem;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.duet-date {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Duet Date Picker</h1>
|
||||
<p>
|
||||
Duet Date Picker is an open source version of
|
||||
<a href="https://www.duetds.com">Duet Design System’s</a> accessible date picker. It can be implemented and used
|
||||
across any JavaScript framework or no framework at all.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>
|
||||
For documentation, please see the
|
||||
<a href="https://github.com/duetds/date-picker">GitHub repository</a>.
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<button class="theme">Switch theme</button>
|
||||
<script>
|
||||
var stylesheet = document.getElementById("theme")
|
||||
var theme = document.querySelector(".theme")
|
||||
|
||||
theme.addEventListener("click", function() {
|
||||
if (!theme.classList.contains("active")) {
|
||||
stylesheet.setAttribute("href", "/date-picker/themes/dark.css")
|
||||
document.documentElement.classList.add("dark-theme")
|
||||
theme.classList.add("active")
|
||||
} else {
|
||||
stylesheet.setAttribute("href", "/date-picker/themes/default.css")
|
||||
document.documentElement.classList.remove("dark-theme")
|
||||
theme.classList.remove("active")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<h2>Default</h2>
|
||||
<label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker></code></pre>
|
||||
|
||||
<h2>Using show() method</h2>
|
||||
<label for="date2">Choose a date</label>
|
||||
<duet-date-picker class="cal" identifier="date2"></duet-date-picker>
|
||||
<button type="button" class="button show">Show date picker</button>
|
||||
<script>
|
||||
var button = document.querySelector(".show")
|
||||
|
||||
button.addEventListener("click", function() {
|
||||
document.querySelector(".cal").show()
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
<button type="button">Show date picker</button>
|
||||
|
||||
<script>
|
||||
const button = document.querySelector("button")
|
||||
|
||||
button.addEventListener("click", function() {
|
||||
document.querySelector("duet-date-picker").show()
|
||||
});
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Using setFocus() method</h2>
|
||||
<label for="date3">Choose a date</label>
|
||||
<duet-date-picker class="cal2" identifier="date3"></duet-date-picker>
|
||||
<button type="button" class="button focus">Focus date picker</button>
|
||||
<script>
|
||||
var button = document.querySelector(".focus")
|
||||
|
||||
button.addEventListener("click", function() {
|
||||
document.querySelector(".cal2").setFocus()
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
<button type="button">Focus date picker</button>
|
||||
|
||||
<script>
|
||||
const button = document.querySelector("button")
|
||||
|
||||
button.addEventListener("click", function() {
|
||||
document.querySelector("duet-date-picker").setFocus()
|
||||
});
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Getting selected value</h2>
|
||||
<label for="date4">Choose a date</label>
|
||||
<duet-date-picker class="picker" identifier="date4"></duet-date-picker>
|
||||
<output>undefined</output>
|
||||
<script>
|
||||
var picker = document.querySelector(".picker")
|
||||
var output = document.querySelector("output")
|
||||
|
||||
picker.addEventListener("duetChange", function(event) {
|
||||
output.innerHTML = event.detail.valueAsDate
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
<output>undefined</output>
|
||||
|
||||
<script>
|
||||
const picker = document.querySelector("duet-date-picker")
|
||||
const output = document.querySelector("output")
|
||||
|
||||
picker.addEventListener("duetChange", function(event) {
|
||||
output.innerHTML = event.detail.valueAsDate
|
||||
});
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Predefined value</h2>
|
||||
<label for="date5">Choose a date</label>
|
||||
<duet-date-picker identifier="date5" value="2020-06-16"></duet-date-picker>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date" value="2020-06-16">
|
||||
</duet-date-picker></code></pre>
|
||||
|
||||
<h2>Minimum and maximum date</h2>
|
||||
<label for="date6">Choose a date</label>
|
||||
<duet-date-picker identifier="date6" min="1990-06-10" max="2020-07-18" value="2020-06-16"></duet-date-picker>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date" min="1990-06-10"
|
||||
max="2020-07-18" value="2020-06-16">
|
||||
</duet-date-picker></code></pre>
|
||||
|
||||
<h2>Localization</h2>
|
||||
<label for="date7" lang="fi">Valitse päivämäärä</label>
|
||||
<duet-date-picker class="picker-fi" lang="fi" identifier="date7"></duet-date-picker>
|
||||
<script>
|
||||
var pickerFinnish = document.querySelector(".picker-fi")
|
||||
var DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
|
||||
|
||||
pickerFinnish.dateAdapter = {
|
||||
parse: function parse() {
|
||||
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
|
||||
var createDate = arguments.length > 1 ? arguments[1] : undefined
|
||||
var matches = value.match(DATE_FORMAT)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format: function format(date) {
|
||||
return ""
|
||||
.concat(date.getDate(), ".")
|
||||
.concat(date.getMonth() + 1, ".")
|
||||
.concat(date.getFullYear())
|
||||
},
|
||||
}
|
||||
|
||||
pickerFinnish.localization = {
|
||||
buttonLabel: "Valitse päivämäärä",
|
||||
placeholder: "pp.kk.vvvv",
|
||||
selectedDateMessage: "Valittu päivämäärä on",
|
||||
prevMonthLabel: "Edellinen kuukausi",
|
||||
nextMonthLabel: "Seuraava kuukausi",
|
||||
monthSelectLabel: "Kuukausi",
|
||||
yearSelectLabel: "Vuosi",
|
||||
closeLabel: "Sulje ikkuna",
|
||||
calendarHeading: "Valitse päivämäärä",
|
||||
dayNames: ["Sunnuntai", "Maanantai", "Tiistai", "Keskiviikko", "Torstai", "Perjantai", "Lauantai"],
|
||||
monthNames: [
|
||||
"Tammikuu",
|
||||
"Helmikuu",
|
||||
"Maaliskuu",
|
||||
"Huhtikuu",
|
||||
"Toukokuu",
|
||||
"Kesäkuu",
|
||||
"Heinäkuu",
|
||||
"Elokuu",
|
||||
"Syyskuu",
|
||||
"Lokakuu",
|
||||
"Marraskuu",
|
||||
"Joulukuu",
|
||||
],
|
||||
monthNamesShort: [
|
||||
"Tammi",
|
||||
"Helmi",
|
||||
"Maalis",
|
||||
"Huhti",
|
||||
"Touko",
|
||||
"Kesä",
|
||||
"Heinä",
|
||||
"Elo",
|
||||
"Syys",
|
||||
"Loka",
|
||||
"Marras",
|
||||
"Joulu",
|
||||
],
|
||||
locale: "fi-FI",
|
||||
}
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Valitse päivämäärä</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
const picker = document.querySelector("duet-date-picker")
|
||||
const DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
|
||||
|
||||
picker.dateAdapter = {
|
||||
parse(value = "", createDate) {
|
||||
const matches = value.match(DATE_FORMAT)
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format(date) {
|
||||
return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`
|
||||
},
|
||||
}
|
||||
|
||||
picker.localization = {
|
||||
buttonLabel: "Valitse päivämäärä",
|
||||
placeholder: "pp.kk.vvvv",
|
||||
selectedDateMessage: "Valittu päivämäärä on",
|
||||
prevMonthLabel: "Edellinen kuukausi",
|
||||
nextMonthLabel: "Seuraava kuukausi",
|
||||
monthSelectLabel: "Kuukausi",
|
||||
yearSelectLabel: "Vuosi",
|
||||
closeLabel: "Sulje ikkuna",
|
||||
calendarHeading: "Valitse päivämäärä",
|
||||
dayNames: ["Sunnuntai", "Maanantai", "Tiistai", "Keskiviikko", "Torstai", "Perjantai", "Lauantai"],
|
||||
monthNames: ["Tammikuu", "Helmikuu", "Maaliskuu", "Huhtikuu", "Toukokuu", "Kesäkuu", "Heinäkuu", "Elokuu", "Syyskuu", "Lokakuu", "Marraskuu", "Joulukuu"],
|
||||
monthNamesShort: ["Tammi", "Helmi", "Maalis", "Huhti", "Touko", "Kesä", "Heinä", "Elo", "Syys", "Loka", "Marras", "Joulu"],
|
||||
locale: "fi-FI",
|
||||
}
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Changing first day of week and date format</h2>
|
||||
<label for="date8">Choose a date</label>
|
||||
<duet-date-picker first-day-of-week="0" class="picker-week" identifier="date8"></duet-date-picker>
|
||||
<script>
|
||||
var pickerWeek = document.querySelector(".picker-week")
|
||||
var DATE_FORMAT_US = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/
|
||||
|
||||
pickerWeek.dateAdapter = {
|
||||
parse: function parse() {
|
||||
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
|
||||
var createDate = arguments.length > 1 ? arguments[1] : undefined
|
||||
var matches = value.match(DATE_FORMAT_US)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[1], matches[2])
|
||||
}
|
||||
},
|
||||
format: function format(date) {
|
||||
return ""
|
||||
.concat(date.getMonth() + 1, "/")
|
||||
.concat(date.getDate(), "/")
|
||||
.concat(date.getFullYear())
|
||||
},
|
||||
}
|
||||
|
||||
pickerWeek.localization = {
|
||||
buttonLabel: "Choose date",
|
||||
placeholder: "mm/dd/yyyy",
|
||||
selectedDateMessage: "Selected date is",
|
||||
prevMonthLabel: "Previous month",
|
||||
nextMonthLabel: "Next month",
|
||||
monthSelectLabel: "Month",
|
||||
yearSelectLabel: "Year",
|
||||
closeLabel: "Close window",
|
||||
calendarHeading: "Choose a date",
|
||||
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
monthNames: [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
locale: "en-US",
|
||||
}
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker first-day-of-week="0" identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
const picker = document.querySelector("duet-date-picker")
|
||||
const DATE_FORMAT_US = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/
|
||||
|
||||
picker.dateAdapter = {
|
||||
parse(value = "", createDate) {
|
||||
const matches = value.match(DATE_FORMAT_US)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[1], matches[2])
|
||||
}
|
||||
},
|
||||
format(date) {
|
||||
return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`
|
||||
},
|
||||
}
|
||||
|
||||
picker.localization = {
|
||||
buttonLabel: "Choose date",
|
||||
placeholder: "mm/dd/yyyy",
|
||||
selectedDateMessage: "Selected date is",
|
||||
prevMonthLabel: "Previous month",
|
||||
nextMonthLabel: "Next month",
|
||||
monthSelectLabel: "Month",
|
||||
yearSelectLabel: "Year",
|
||||
closeLabel: "Close window",
|
||||
calendarHeading: "Choose a date",
|
||||
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
||||
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
locale: "en-US",
|
||||
}
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Required atrribute</h2>
|
||||
<label for="date9">Choose a date (required)</label>
|
||||
<form class="form-picker-required">
|
||||
<duet-date-picker required identifier="date9"></duet-date-picker>
|
||||
<button type="submit" class="button">Submit form</button>
|
||||
</form>
|
||||
<script>
|
||||
const form = document.querySelector(".form-picker-required")
|
||||
form.addEventListener("submit", function(e) {
|
||||
e.preventDefault()
|
||||
alert("Submitted")
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><form class="form-picker-required">
|
||||
<label for="date">Choose a date</label>
|
||||
<duet-date-picker required identifier="date"></duet-date-picker>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector(".form-picker-required")
|
||||
form.addEventListener("submit", function(e) {
|
||||
e.preventDefault()
|
||||
alert("Submitted")
|
||||
})
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Disable selectable days</h2>
|
||||
<label for="dateDisabledWeekend">Choose a date</label>
|
||||
<duet-date-picker class="picker-disabled-weekend" identifier="dateDisabledWeekend"></duet-date-picker>
|
||||
<script>
|
||||
function isWeekend(date) {
|
||||
return date.getDay() === 0 || date.getDay() === 6
|
||||
}
|
||||
|
||||
const pickerDisableWeekend = document.querySelector(".picker-disabled-weekend")
|
||||
pickerDisableWeekend.isDateDisabled = isWeekend
|
||||
|
||||
pickerDisableWeekend.addEventListener("duetChange", function(e) {
|
||||
if (isWeekend(e.detail.valueAsDate)) {
|
||||
alert("Please select a weekday")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
function isWeekend(date) {
|
||||
return date.getDay() === 0 || date.getDay() === 6
|
||||
}
|
||||
|
||||
const pickerDisableWeekend = document.querySelector("duet-date-picker")
|
||||
pickerDisableWeekend.isDateDisabled = isWeekend
|
||||
|
||||
pickerDisableWeekend.addEventListener("duetChange", function(e) {
|
||||
if (isWeekend(e.detail.valueAsDate)) {
|
||||
alert("Please select a weekday")
|
||||
}
|
||||
})
|
||||
</script></code></pre>
|
||||
<br />
|
||||
<p>
|
||||
© 2020 LocalTapiola Services Ltd /
|
||||
<a href="https://www.duetds.com">Duet Design System</a>.<br />Licensed under the MIT license.
|
||||
</p>
|
||||
</main>
|
||||
<div class="loader">Loading…</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,17 @@
|
||||
:root {
|
||||
--duet-color-primary: #005fcc;
|
||||
--duet-color-text: #fff;
|
||||
--duet-color-text-active: #fff;
|
||||
--duet-color-placeholder: #aaa;
|
||||
--duet-color-button: #444;
|
||||
--duet-color-surface: #222;
|
||||
--duet-color-overlay: rgba(0, 0, 0, 0.8);
|
||||
--duet-color-border: #fff;
|
||||
|
||||
--duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--duet-font-normal: 400;
|
||||
--duet-font-bold: 600;
|
||||
|
||||
--duet-radius: 4px;
|
||||
--duet-z-index: 600;
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
:root {
|
||||
--duet-color-primary: #005fcc;
|
||||
--duet-color-text: #333;
|
||||
--duet-color-text-active: #fff;
|
||||
--duet-color-placeholder: #666;
|
||||
--duet-color-button: #f5f5f5;
|
||||
--duet-color-surface: #fff;
|
||||
--duet-color-overlay: rgba(0, 0, 0, 0.8);
|
||||
--duet-color-border: #333;
|
||||
|
||||
--duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--duet-font-normal: 400;
|
||||
--duet-font-bold: 600;
|
||||
|
||||
--duet-radius: 4px;
|
||||
--duet-z-index: 600;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@duetds/date-picker/hydrate",
|
||||
"description": "duet component hydration app.",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts"
|
||||
}
|
||||
|
After Width: | Height: | Size: 47 KiB |
@ -0,0 +1,10 @@
|
||||
const { configureToMatchImageSnapshot } = require("jest-image-snapshot")
|
||||
|
||||
const toMatchImageSnapshot = configureToMatchImageSnapshot({
|
||||
failureThreshold: 300,
|
||||
customDiffConfig: {
|
||||
threshold: 0.2,
|
||||
},
|
||||
})
|
||||
|
||||
expect.extend({ toMatchImageSnapshot })
|
||||
@ -0,0 +1,128 @@
|
||||
{
|
||||
"name": "@duetds/date-picker",
|
||||
"version": "1.4.0",
|
||||
"description": "Duet Date Picker is an open source version of Duet Design System’s accessible date picker.",
|
||||
"author": "LocalTapiola Services Ltd <duetdesignsystem@lahitapiola.fi>",
|
||||
"license": "MIT",
|
||||
"module": "dist/index.js",
|
||||
"es2015": "dist/esm/index.js",
|
||||
"es2017": "dist/esm/index.js",
|
||||
"jsnext:main": "dist/esm/index.js",
|
||||
"main": "dist/index.cjs.js",
|
||||
"unpkg": "dist/duet/duet.js",
|
||||
"types": "dist/types/components.d.ts",
|
||||
"collection": "dist/collection/collection-manifest.json",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/duetds/date-picker.git"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"custom-element",
|
||||
"dist",
|
||||
"hydrate/index.js",
|
||||
"hydrate/index.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "npm run dev",
|
||||
"dev": "stencil build --dev --es5 --watch --serve",
|
||||
"docs": "stencil build --docs-readme",
|
||||
"build": "stencil build --es5",
|
||||
"precommit": "stencil test --spec --silent",
|
||||
"test": "stencil test --spec --e2e --silent",
|
||||
"test:dev": "stencil test --spec --e2e --watchAll",
|
||||
"test:unit": "stencil test --spec --silent",
|
||||
"test:e2e": "stencil test --e2e --silent",
|
||||
"lint:js": "eslint \"**/*.{js,ts,tsx}\" --cache --quiet",
|
||||
"lint:sass": "sass-lint -c ./.sasslintrc.json",
|
||||
"lint:sass:fix": "sass-lint-auto-fix",
|
||||
"preversion": "npm run lint:js && npm run lint:sass && npm test",
|
||||
"version": "npm run build",
|
||||
"postversion": "npm publish",
|
||||
"postpublish": "git push origin master --tags"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "pretty-quick --staged",
|
||||
"pre-push": "npm run lint:js && npm run lint:sass"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.17.0",
|
||||
"npm": ">= 6.14.0"
|
||||
},
|
||||
"bugs": {
|
||||
"email": "duetdesignsystem@lahitapiola.fi"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stencil/sass": "1.3.2",
|
||||
"@stencil/utils": "latest",
|
||||
"@types/jest": "26.0.10",
|
||||
"@types/jest-image-snapshot": "3.1.0",
|
||||
"@types/puppeteer": "3.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "2.13.0",
|
||||
"@typescript-eslint/parser": "2.13.0",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-config-prettier": "6.7.0",
|
||||
"eslint-plugin-prettier": "3.1.2",
|
||||
"husky": "4.2.5",
|
||||
"jest": "26.4.1",
|
||||
"jest-cli": "26.4.1",
|
||||
"jest-image-snapshot": "4.1.0",
|
||||
"prettier": "1.19.1",
|
||||
"prettier-stylelint": "0.4.2",
|
||||
"pretty-quick": "^2.0.1",
|
||||
"puppeteer": "5.2.1",
|
||||
"sass-lint": "1.13.1",
|
||||
"sass-lint-auto-fix": "0.21.2",
|
||||
"typescript": "3.9.7"
|
||||
},
|
||||
"sasslintConfig": "./.sasslintrc.json",
|
||||
"eslintConfig": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"accessibility": "off",
|
||||
"ecmaVersion": 2018,
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"only-multiline"
|
||||
],
|
||||
"curly": [
|
||||
"error",
|
||||
"all"
|
||||
],
|
||||
"no-console": "off",
|
||||
"no-undef": "off",
|
||||
"no-var": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { PrerenderConfig } from "@stencil/core"
|
||||
|
||||
export const config: PrerenderConfig = {
|
||||
hydrateOptions(url) {
|
||||
return {
|
||||
prettyHtml: false,
|
||||
clientHydrateAnnotations: true,
|
||||
removeScripts: false,
|
||||
removeUnusedStyles: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,13 @@
|
||||
import { parseISODate, printISODate, createDate } from "./date-utils"
|
||||
|
||||
type CreateDate = typeof createDate
|
||||
export type DuetDateParser = (input: string, createDate: CreateDate) => Date | undefined
|
||||
export type DuetDateFormatter = (date: Date) => string
|
||||
|
||||
export interface DuetDateAdapter {
|
||||
parse: DuetDateParser
|
||||
format: DuetDateFormatter
|
||||
}
|
||||
|
||||
const isoAdapter: DuetDateAdapter = { parse: parseISODate, format: printISODate }
|
||||
export default isoAdapter
|
||||
@ -0,0 +1,49 @@
|
||||
type MonthsNames = [string, string, string, string, string, string, string, string, string, string, string, string]
|
||||
type DayNames = [string, string, string, string, string, string, string]
|
||||
|
||||
export type DuetLocalizedText = {
|
||||
buttonLabel: string
|
||||
placeholder: string
|
||||
selectedDateMessage: string
|
||||
prevMonthLabel: string
|
||||
nextMonthLabel: string
|
||||
monthSelectLabel: string
|
||||
yearSelectLabel: string
|
||||
closeLabel: string
|
||||
calendarHeading: string
|
||||
dayNames: DayNames
|
||||
monthNames: MonthsNames
|
||||
monthNamesShort: MonthsNames
|
||||
locale: string | string[]
|
||||
}
|
||||
|
||||
const localization: DuetLocalizedText = {
|
||||
buttonLabel: "Choose date",
|
||||
placeholder: "YYYY-MM-DD",
|
||||
selectedDateMessage: "Selected date is",
|
||||
prevMonthLabel: "Previous month",
|
||||
nextMonthLabel: "Next month",
|
||||
monthSelectLabel: "Month",
|
||||
yearSelectLabel: "Year",
|
||||
closeLabel: "Close window",
|
||||
calendarHeading: "Choose a date",
|
||||
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
monthNames: [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
locale: "en-GB",
|
||||
}
|
||||
|
||||
export default localization
|
||||
@ -0,0 +1,65 @@
|
||||
import { h, FunctionalComponent } from "@stencil/core"
|
||||
import { isEqual, isEqualMonth } from "./date-utils"
|
||||
|
||||
export type DatePickerDayProps = {
|
||||
focusedDay: Date
|
||||
today: Date
|
||||
day: Date
|
||||
disabled: boolean
|
||||
inRange: boolean
|
||||
isSelected: boolean
|
||||
dateFormatter: Intl.DateTimeFormat
|
||||
onDaySelect: (event: MouseEvent, day: Date) => void
|
||||
onKeyboardNavigation: (event: KeyboardEvent) => void
|
||||
focusedDayRef?: (element: HTMLElement) => void
|
||||
}
|
||||
|
||||
export const DatePickerDay: FunctionalComponent<DatePickerDayProps> = ({
|
||||
focusedDay,
|
||||
today,
|
||||
day,
|
||||
onDaySelect,
|
||||
onKeyboardNavigation,
|
||||
focusedDayRef,
|
||||
disabled,
|
||||
inRange,
|
||||
isSelected,
|
||||
dateFormatter,
|
||||
}) => {
|
||||
const isToday = isEqual(day, today)
|
||||
const isMonth = isEqualMonth(day, focusedDay)
|
||||
const isFocused = isEqual(day, focusedDay)
|
||||
const isOutsideRange = !inRange
|
||||
|
||||
function handleClick(e) {
|
||||
onDaySelect(e, day)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
class={{
|
||||
"duet-date__day": true,
|
||||
"is-outside": isOutsideRange,
|
||||
"is-today": isToday,
|
||||
"is-month": isMonth,
|
||||
"is-disabled": disabled,
|
||||
}}
|
||||
tabIndex={isFocused ? 0 : -1}
|
||||
onClick={handleClick}
|
||||
onKeyDown={onKeyboardNavigation}
|
||||
aria-disabled={disabled ? "true" : undefined}
|
||||
disabled={isOutsideRange}
|
||||
type="button"
|
||||
aria-pressed={isSelected ? "true" : "false"}
|
||||
aria-current={isToday ? "date" : undefined}
|
||||
ref={el => {
|
||||
if (isFocused && el && focusedDayRef) {
|
||||
focusedDayRef(el)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span aria-hidden="true">{day.getDate()}</span>
|
||||
<span class="duet-date__vhidden">{dateFormatter.format(day)}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
import { h, FunctionalComponent } from "@stencil/core"
|
||||
import { DuetLocalizedText } from "./date-localization"
|
||||
|
||||
type DatePickerInputProps = {
|
||||
value: string
|
||||
formattedValue: string
|
||||
valueAsDate: Date
|
||||
localization: DuetLocalizedText
|
||||
name: string
|
||||
identifier: string
|
||||
disabled: boolean
|
||||
required: boolean
|
||||
role: string
|
||||
dateFormatter: Intl.DateTimeFormat
|
||||
onClick: (event: MouseEvent) => void
|
||||
onInput: (event: InputEvent) => void
|
||||
onBlur: (event: FocusEvent) => void
|
||||
onFocus: (event: FocusEvent) => void
|
||||
buttonRef: (element: HTMLButtonElement) => void
|
||||
inputRef: (element: HTMLInputElement) => void
|
||||
}
|
||||
|
||||
export const DatePickerInput: FunctionalComponent<DatePickerInputProps> = ({
|
||||
onClick,
|
||||
dateFormatter,
|
||||
localization,
|
||||
name,
|
||||
formattedValue,
|
||||
valueAsDate,
|
||||
value,
|
||||
identifier,
|
||||
disabled,
|
||||
required,
|
||||
role,
|
||||
buttonRef,
|
||||
inputRef,
|
||||
onInput,
|
||||
onBlur,
|
||||
onFocus,
|
||||
}) => {
|
||||
return (
|
||||
<div class="duet-date__input-wrapper">
|
||||
<input
|
||||
class="duet-date__input"
|
||||
value={formattedValue}
|
||||
placeholder={localization.placeholder}
|
||||
id={identifier}
|
||||
disabled={disabled}
|
||||
role={role}
|
||||
required={required ? true : undefined}
|
||||
aria-autocomplete="none"
|
||||
onInput={onInput}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
autoComplete="off"
|
||||
ref={inputRef}
|
||||
/>
|
||||
<input type="hidden" name={name} value={value} />
|
||||
<button class="duet-date__toggle" onClick={onClick} disabled={disabled} ref={buttonRef} type="button">
|
||||
<span class="duet-date__toggle-icon">
|
||||
<svg aria-hidden="true" height="24" viewBox="0 0 21 21" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path
|
||||
d="m2.5.5h12c1.1045695 0 2 .8954305 2 2v12c0 1.1045695-.8954305 2-2 2h-12c-1.1045695 0-2-.8954305-2-2v-12c0-1.1045695.8954305-2 2-2z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path d="m.5 4.5h16" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<g fill="currentColor">
|
||||
<circle cx="8.5" cy="8.5" r="1" />
|
||||
<circle cx="4.5" cy="8.5" r="1" />
|
||||
<circle cx="12.5" cy="8.5" r="1" />
|
||||
<circle cx="8.5" cy="12.5" r="1" />
|
||||
<circle cx="4.5" cy="12.5" r="1" />
|
||||
<circle cx="12.5" cy="12.5" r="1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="duet-date__vhidden">
|
||||
{localization.buttonLabel}
|
||||
{valueAsDate && (
|
||||
<span>
|
||||
, {localization.selectedDateMessage} {dateFormatter.format(valueAsDate)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
import { h, FunctionalComponent } from "@stencil/core"
|
||||
import { DuetLocalizedText } from "./date-localization"
|
||||
import { DatePickerDay, DatePickerDayProps } from "./date-picker-day"
|
||||
import { getViewOfMonth, inRange, DaysOfWeek, isEqual } from "./date-utils"
|
||||
import { DateDisabledPredicate } from "./duet-date-picker"
|
||||
|
||||
function chunk<T>(array: T[], chunkSize: number): T[][] {
|
||||
const result = []
|
||||
|
||||
for (let i = 0; i < array.length; i += chunkSize) {
|
||||
result.push(array.slice(i, i + chunkSize))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function mapWithOffset<T, U>(array: T[], startingOffset: number, mapFn: (item: T) => U): U[] {
|
||||
return array.map((_, i) => {
|
||||
const adjustedIndex = (i + startingOffset) % array.length
|
||||
return mapFn(array[adjustedIndex])
|
||||
})
|
||||
}
|
||||
|
||||
type DatePickerMonthProps = {
|
||||
selectedDate: Date
|
||||
focusedDate: Date
|
||||
labelledById: string
|
||||
localization: DuetLocalizedText
|
||||
firstDayOfWeek: DaysOfWeek
|
||||
min?: Date
|
||||
max?: Date
|
||||
dateFormatter: Intl.DateTimeFormat
|
||||
isDateDisabled: DateDisabledPredicate
|
||||
onDateSelect: DatePickerDayProps["onDaySelect"]
|
||||
onKeyboardNavigation: DatePickerDayProps["onKeyboardNavigation"]
|
||||
focusedDayRef: (element: HTMLElement) => void
|
||||
}
|
||||
|
||||
export const DatePickerMonth: FunctionalComponent<DatePickerMonthProps> = ({
|
||||
selectedDate,
|
||||
focusedDate,
|
||||
labelledById,
|
||||
localization,
|
||||
firstDayOfWeek,
|
||||
min,
|
||||
max,
|
||||
dateFormatter,
|
||||
isDateDisabled,
|
||||
onDateSelect,
|
||||
onKeyboardNavigation,
|
||||
focusedDayRef,
|
||||
}) => {
|
||||
const today = new Date()
|
||||
const days = getViewOfMonth(focusedDate, firstDayOfWeek)
|
||||
|
||||
return (
|
||||
<table class="duet-date__table" aria-labelledby={labelledById}>
|
||||
<thead>
|
||||
<tr>
|
||||
{mapWithOffset(localization.dayNames, firstDayOfWeek, dayName => (
|
||||
<th class="duet-date__table-header" scope="col">
|
||||
<span aria-hidden="true">{dayName.substr(0, 2)}</span>
|
||||
<span class="duet-date__vhidden">{dayName}</span>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{chunk(days, 7).map(week => (
|
||||
<tr class="duet-date__row">
|
||||
{week.map(day => (
|
||||
<td class="duet-date__cell">
|
||||
<DatePickerDay
|
||||
day={day}
|
||||
today={today}
|
||||
focusedDay={focusedDate}
|
||||
isSelected={isEqual(day, selectedDate)}
|
||||
disabled={isDateDisabled(day)}
|
||||
inRange={inRange(day, min, max)}
|
||||
onDaySelect={onDateSelect}
|
||||
dateFormatter={dateFormatter}
|
||||
onKeyboardNavigation={onKeyboardNavigation}
|
||||
focusedDayRef={focusedDayRef}
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,359 @@
|
||||
import {
|
||||
isEqual,
|
||||
isEqualMonth,
|
||||
addDays,
|
||||
addMonths,
|
||||
addYears,
|
||||
startOfWeek,
|
||||
endOfWeek,
|
||||
setMonth,
|
||||
setYear,
|
||||
inRange,
|
||||
clamp,
|
||||
startOfMonth,
|
||||
endOfMonth,
|
||||
getViewOfMonth,
|
||||
parseISODate,
|
||||
printISODate,
|
||||
DaysOfWeek,
|
||||
} from "./date-utils"
|
||||
|
||||
describe("duet-date-picker/date-utils", () => {
|
||||
describe("parseISODate", () => {
|
||||
it("handles falsy values", () => {
|
||||
// @ts-ignore
|
||||
expect(parseISODate()).toBeUndefined()
|
||||
// @ts-ignore
|
||||
expect(parseISODate(false)).toBeUndefined()
|
||||
// @ts-ignore
|
||||
expect(parseISODate("")).toBeUndefined()
|
||||
// @ts-ignore
|
||||
expect(parseISODate(null)).toBeUndefined()
|
||||
// @ts-ignore
|
||||
expect(parseISODate(0)).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns undefined for invalid strings", () => {
|
||||
// invalid format
|
||||
expect(parseISODate("hello world")).toBeUndefined()
|
||||
expect(parseISODate("01/01/2020")).toBeUndefined()
|
||||
expect(parseISODate("01.01.2020")).toBeUndefined()
|
||||
expect(parseISODate("01-01-2020")).toBeUndefined()
|
||||
expect(parseISODate("2020/01/01")).toBeUndefined()
|
||||
// expect(parseISODate("2020-01-01")).toBeUndefined()
|
||||
expect(parseISODate("2020--01--01")).toBeUndefined()
|
||||
expect(parseISODate("19-01-01")).toBeUndefined()
|
||||
expect(parseISODate("190-01-01")).toBeUndefined()
|
||||
expect(parseISODate("2020-000001-000001")).toBeUndefined()
|
||||
expect(parseISODate("0xAA-01-01")).toBeUndefined()
|
||||
|
||||
// correct format, but invalid dates
|
||||
expect(parseISODate("2020-12-32")).toBeUndefined()
|
||||
expect(parseISODate("2020-13-01")).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns a date for valid strings", () => {
|
||||
expect(parseISODate("2020-01-01")).toEqual(new Date(2020, 0, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe("isEqual", () => {
|
||||
it("compares dates", () => {
|
||||
expect(isEqual(new Date(2020, 0, 1), new Date(2020, 0, 1))).toBe(true)
|
||||
expect(isEqual(new Date(2020, 0, 1), new Date(2020, 0, 2))).toBe(false)
|
||||
|
||||
expect(isEqual(null, new Date(2020, 0, 1))).toBe(false)
|
||||
expect(isEqual(new Date(2020, 0, 1), null)).toBe(false)
|
||||
expect(isEqual(null, null)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isEqualMonth", () => {
|
||||
it("compares dates", () => {
|
||||
expect(isEqualMonth(new Date(2020, 0, 1), new Date(2020, 0, 1))).toBe(true)
|
||||
expect(isEqualMonth(new Date(2020, 0, 1), new Date(2020, 0, 31))).toBe(true)
|
||||
|
||||
expect(isEqualMonth(new Date(2020, 0, 1), new Date(2020, 1, 1))).toBe(false)
|
||||
expect(isEqualMonth(new Date(2020, 0, 1), new Date(2021, 0, 1))).toBe(false)
|
||||
|
||||
expect(isEqualMonth(null, new Date(2020, 0, 1))).toBe(false)
|
||||
expect(isEqualMonth(new Date(2020, 0, 1), null)).toBe(false)
|
||||
expect(isEqualMonth(null, null)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("printISODate", () => {
|
||||
it("should print in format dd.mm.yyyy", () => {
|
||||
expect(printISODate(new Date(2020, 0, 1))).toBe("2020-01-01")
|
||||
expect(printISODate(new Date(2020, 8, 9))).toBe("2020-09-09")
|
||||
expect(printISODate(new Date(2020, 9, 10))).toBe("2020-10-10")
|
||||
})
|
||||
|
||||
it("returns empty string for undefined dates", () => {
|
||||
expect(printISODate(undefined)).toBe("")
|
||||
})
|
||||
})
|
||||
|
||||
describe("addDays", () => {
|
||||
it("can add days", () => {
|
||||
const date = new Date(2020, 0, 30)
|
||||
expect(addDays(date, 1)).toEqual(new Date(2020, 0, 31))
|
||||
expect(addDays(date, 7)).toEqual(new Date(2020, 1, 6))
|
||||
expect(addDays(date, 366)).toEqual(new Date(2021, 0, 30))
|
||||
})
|
||||
|
||||
it("can subtract days", () => {
|
||||
const date = new Date(2020, 0, 31)
|
||||
expect(addDays(date, -1)).toEqual(new Date(2020, 0, 30))
|
||||
expect(addDays(date, -2)).toEqual(new Date(2020, 0, 29))
|
||||
expect(addDays(date, -7)).toEqual(new Date(2020, 0, 24))
|
||||
})
|
||||
})
|
||||
|
||||
describe("addMonths", () => {
|
||||
it("can add months", () => {
|
||||
const date = new Date(2020, 0, 1)
|
||||
expect(addMonths(date, 1)).toEqual(new Date(2020, 1, 1))
|
||||
expect(addMonths(date, 2)).toEqual(new Date(2020, 2, 1))
|
||||
expect(addMonths(date, 12)).toEqual(new Date(2021, 0, 1))
|
||||
})
|
||||
|
||||
it("can subtract months", () => {
|
||||
const date = new Date(2020, 2, 1)
|
||||
expect(addMonths(date, -1)).toEqual(new Date(2020, 1, 1))
|
||||
expect(addMonths(date, -2)).toEqual(new Date(2020, 0, 1))
|
||||
expect(addMonths(date, -12)).toEqual(new Date(2019, 2, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe("addYears", () => {
|
||||
it("can add years", () => {
|
||||
const date = new Date(2020, 0, 1)
|
||||
expect(addYears(date, 1)).toEqual(new Date(2021, 0, 1))
|
||||
expect(addYears(date, 10)).toEqual(new Date(2030, 0, 1))
|
||||
})
|
||||
|
||||
it("can subtract years", () => {
|
||||
const date = new Date(2020, 0, 1)
|
||||
expect(addYears(date, -1)).toEqual(new Date(2019, 0, 1))
|
||||
expect(addYears(date, -10)).toEqual(new Date(2010, 0, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe("startOfWeek", () => {
|
||||
it("returns the first day of the week", () => {
|
||||
expect(startOfWeek(new Date(2020, 0, 1))).toEqual(new Date(2019, 11, 30))
|
||||
})
|
||||
|
||||
it("returns the same date if already start of the week", () => {
|
||||
const start = startOfWeek(new Date(2020, 0, 1))
|
||||
expect(startOfWeek(start)).toEqual(start)
|
||||
})
|
||||
|
||||
it("supports changing the first day of the week", () => {
|
||||
expect(startOfWeek(new Date(2020, 0, 1), DaysOfWeek.Sunday)).toEqual(new Date(2019, 11, 29))
|
||||
})
|
||||
})
|
||||
|
||||
describe("endOfWeek", () => {
|
||||
it("returns the first day of the week", () => {
|
||||
expect(endOfWeek(new Date(2020, 0, 1))).toEqual(new Date(2020, 0, 5))
|
||||
})
|
||||
|
||||
it("returns the same date if already start of the week", () => {
|
||||
const end = endOfWeek(new Date(2020, 0, 1))
|
||||
expect(endOfWeek(end)).toEqual(end)
|
||||
})
|
||||
|
||||
it("supports changing the first day of the week", () => {
|
||||
expect(endOfWeek(new Date(2020, 0, 1), DaysOfWeek.Sunday)).toEqual(new Date(2020, 0, 4))
|
||||
})
|
||||
})
|
||||
|
||||
describe("setMonths", () => {
|
||||
it("sets the month and returns a new date", () => {
|
||||
const date = new Date(2020, 0, 1)
|
||||
const result = setMonth(date, 1)
|
||||
|
||||
expect(result).not.toBe(date)
|
||||
expect(result).toEqual(new Date(2020, 1, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe("setYears", () => {
|
||||
it("sets the year and returns a new date", () => {
|
||||
const date = new Date(2020, 0, 1)
|
||||
const result = setYear(date, 2021)
|
||||
|
||||
expect(result).not.toBe(date)
|
||||
expect(result).toEqual(new Date(2021, 0, 1))
|
||||
})
|
||||
})
|
||||
|
||||
describe("inRange", () => {
|
||||
it("returns false for dates below min", () => {
|
||||
const min = new Date(2020, 0, 1)
|
||||
const max = new Date(2020, 11, 31)
|
||||
const date = new Date(2019, 1, 1)
|
||||
|
||||
expect(inRange(date, min, max)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns false for dates above max", () => {
|
||||
const min = new Date(2020, 0, 1)
|
||||
const max = new Date(2020, 11, 31)
|
||||
const date = new Date(2021, 1, 1)
|
||||
|
||||
expect(inRange(date, min, max)).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true for dates in range", () => {
|
||||
const min = new Date(2020, 0, 1)
|
||||
const max = new Date(2020, 11, 31)
|
||||
const date = new Date(2020, 1, 1)
|
||||
|
||||
expect(inRange(date, min, max)).toBe(true)
|
||||
expect(inRange(min, min, max)).toBe(true)
|
||||
expect(inRange(max, min, max)).toBe(true)
|
||||
})
|
||||
|
||||
it("supports only specifying a minimum", () => {
|
||||
const min = new Date(2020, 0, 1)
|
||||
|
||||
expect(inRange(new Date(2020, 1, 1), min)).toBe(true)
|
||||
expect(inRange(min, min)).toBe(true)
|
||||
expect(inRange(new Date(2019, 0, 1), min)).toBe(false)
|
||||
})
|
||||
|
||||
it("supports only specifying a maximum", () => {
|
||||
const max = new Date(2020, 1, 1)
|
||||
|
||||
expect(inRange(new Date(2020, 0, 1), undefined, max)).toBe(true)
|
||||
expect(inRange(max, undefined, max)).toBe(true)
|
||||
expect(inRange(new Date(2021, 0, 1), undefined, max)).toBe(false)
|
||||
})
|
||||
|
||||
it("handles undefined min and max", () => {
|
||||
expect(inRange(new Date(2020, 0, 1))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("clamp", () => {
|
||||
it("returns min date for dates below min", () => {
|
||||
const min = new Date(2020, 0, 1)
|
||||
const max = new Date(2020, 11, 31)
|
||||
const date = new Date(2019, 11, 31)
|
||||
|
||||
expect(clamp(date, min, max)).toBe(min)
|
||||
})
|
||||
|
||||
it("returns max date for dates above max", () => {
|
||||
const min = new Date(2020, 0, 1)
|
||||
const max = new Date(2020, 11, 31)
|
||||
const date = new Date(2021, 0, 1)
|
||||
|
||||
expect(clamp(date, min, max)).toBe(max)
|
||||
})
|
||||
|
||||
it("returns date if in range", () => {
|
||||
const min = new Date(2020, 0, 1)
|
||||
const max = new Date(2020, 11, 31)
|
||||
const date = new Date(2020, 5, 1)
|
||||
|
||||
expect(clamp(date, min, max)).toBe(date)
|
||||
expect(clamp(min, min, max)).toBe(min)
|
||||
expect(clamp(max, min, max)).toBe(max)
|
||||
})
|
||||
|
||||
it("supports only specifying a minimum", () => {
|
||||
const min = new Date(2020, 0, 1)
|
||||
const date = new Date(2020, 1, 1)
|
||||
|
||||
expect(clamp(date, min)).toBe(date)
|
||||
expect(clamp(min, min)).toBe(min)
|
||||
})
|
||||
|
||||
it("supports only specifying a maximum", () => {
|
||||
const max = new Date(2020, 1, 1)
|
||||
const date = new Date(2020, 0, 1)
|
||||
|
||||
expect(clamp(date, undefined, max)).toBe(date)
|
||||
expect(clamp(max, undefined, max)).toBe(max)
|
||||
})
|
||||
|
||||
it("handles undefined min and max", () => {
|
||||
const date = new Date(2020, 0, 1)
|
||||
expect(clamp(date)).toBe(date)
|
||||
})
|
||||
})
|
||||
|
||||
describe("startOfMonth", () => {
|
||||
it("returns the first day of the month", () => {
|
||||
for (var i = 0; i < 12; i++) {
|
||||
var date = new Date(2020, i, 10) // arbitrary day in middle of month
|
||||
expect(startOfMonth(date)).toEqual(new Date(2020, i, 1))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("endOfMonth", () => {
|
||||
it("returns the last day of the month", () => {
|
||||
expect(endOfMonth(new Date(2020, 0, 10))).toEqual(new Date(2020, 0, 31)) // jan
|
||||
expect(endOfMonth(new Date(2020, 1, 10))).toEqual(new Date(2020, 1, 29)) // feb (leap year)
|
||||
expect(endOfMonth(new Date(2019, 1, 10))).toEqual(new Date(2019, 1, 28)) // feb (regular year)
|
||||
expect(endOfMonth(new Date(2020, 2, 10))).toEqual(new Date(2020, 2, 31)) // march
|
||||
expect(endOfMonth(new Date(2020, 3, 10))).toEqual(new Date(2020, 3, 30)) // april
|
||||
expect(endOfMonth(new Date(2020, 4, 10))).toEqual(new Date(2020, 4, 31)) // may
|
||||
expect(endOfMonth(new Date(2020, 5, 10))).toEqual(new Date(2020, 5, 30)) // june
|
||||
expect(endOfMonth(new Date(2020, 6, 10))).toEqual(new Date(2020, 6, 31)) // july
|
||||
expect(endOfMonth(new Date(2020, 7, 10))).toEqual(new Date(2020, 7, 31)) // august
|
||||
expect(endOfMonth(new Date(2020, 8, 10))).toEqual(new Date(2020, 8, 30)) // september
|
||||
expect(endOfMonth(new Date(2020, 9, 10))).toEqual(new Date(2020, 9, 31)) // october
|
||||
expect(endOfMonth(new Date(2020, 10, 10))).toEqual(new Date(2020, 10, 30)) // november
|
||||
expect(endOfMonth(new Date(2020, 11, 10))).toEqual(new Date(2020, 11, 31)) // december
|
||||
})
|
||||
})
|
||||
|
||||
describe("getViewOfMonth", () => {
|
||||
function range(from: number, to: number) {
|
||||
var result = []
|
||||
for (var i = 0; i <= to - from; i++) {
|
||||
result.push(from + i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function assertMonth(days: Date[], expected) {
|
||||
expect(days.map(d => d.getDate())).toEqual(expected)
|
||||
}
|
||||
|
||||
it("gives a correct view of the month", () => {
|
||||
// jan
|
||||
assertMonth(getViewOfMonth(new Date(2020, 0, 10)), [30, 31, ...range(1, 31), 1, 2])
|
||||
// feb (leap year)
|
||||
assertMonth(getViewOfMonth(new Date(2020, 1, 10)), [...range(27, 31), ...range(1, 29), 1])
|
||||
// feb (regular year)
|
||||
assertMonth(getViewOfMonth(new Date(2019, 1, 10)), [...range(28, 31), ...range(1, 28), ...range(1, 3)])
|
||||
//march
|
||||
assertMonth(getViewOfMonth(new Date(2020, 2, 10)), [...range(24, 29), ...range(1, 31), ...range(1, 5)])
|
||||
// april
|
||||
assertMonth(getViewOfMonth(new Date(2020, 3, 10)), [30, 31, ...range(1, 30), ...range(1, 3)])
|
||||
// may
|
||||
assertMonth(getViewOfMonth(new Date(2020, 4, 10)), [...range(27, 30), ...range(1, 31)])
|
||||
// june
|
||||
assertMonth(getViewOfMonth(new Date(2020, 5, 10)), [...range(1, 30), ...range(1, 5)])
|
||||
// july
|
||||
assertMonth(getViewOfMonth(new Date(2020, 6, 10)), [29, 30, ...range(1, 31), 1, 2])
|
||||
// august
|
||||
assertMonth(getViewOfMonth(new Date(2020, 7, 10)), [...range(27, 31), ...range(1, 31), ...range(1, 6)])
|
||||
// september
|
||||
assertMonth(getViewOfMonth(new Date(2020, 8, 10)), [31, ...range(1, 30), ...range(1, 4)])
|
||||
// october
|
||||
assertMonth(getViewOfMonth(new Date(2020, 9, 10)), [...range(28, 30), ...range(1, 31), 1])
|
||||
// november
|
||||
assertMonth(getViewOfMonth(new Date(2020, 10, 10)), [...range(26, 31), ...range(1, 30), ...range(1, 6)])
|
||||
// december
|
||||
assertMonth(getViewOfMonth(new Date(2020, 11, 10)), [30, ...range(1, 31), ...range(1, 3)])
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,222 @@
|
||||
const ISO_DATE_FORMAT = /^(\d{4})-(\d{2})-(\d{2})$/
|
||||
|
||||
export enum DaysOfWeek {
|
||||
Sunday = 0,
|
||||
Monday = 1,
|
||||
Tuesday = 2,
|
||||
Wednesday = 3,
|
||||
Thursday = 4,
|
||||
Friday = 5,
|
||||
Saturday = 6,
|
||||
}
|
||||
|
||||
export function createDate(year: string, month: string, day: string): Date {
|
||||
var dayInt = parseInt(day, 10)
|
||||
var monthInt = parseInt(month, 10)
|
||||
var yearInt = parseInt(year, 10)
|
||||
|
||||
const isValid =
|
||||
Number.isInteger(yearInt) && // all parts should be integers
|
||||
Number.isInteger(monthInt) &&
|
||||
Number.isInteger(dayInt) &&
|
||||
monthInt > 0 && // month must be 1-12
|
||||
monthInt <= 12 &&
|
||||
dayInt > 0 && // day must be 1-31
|
||||
dayInt <= 31 &&
|
||||
yearInt > 0
|
||||
|
||||
if (isValid) {
|
||||
return new Date(yearInt, monthInt - 1, dayInt)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value date string in ISO format YYYY-MM-DD
|
||||
*/
|
||||
export function parseISODate(value: string): Date {
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
|
||||
const matches = value.match(ISO_DATE_FORMAT)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[1], matches[2], matches[3])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* print date in format YYYY-MM-DD
|
||||
* @param date
|
||||
*/
|
||||
export function printISODate(date: Date): string {
|
||||
if (!date) {
|
||||
return ""
|
||||
}
|
||||
|
||||
var d = date.getDate().toString(10)
|
||||
var m = (date.getMonth() + 1).toString(10)
|
||||
var y = date.getFullYear().toString(10)
|
||||
|
||||
// days are not zero-indexed, so pad if less than 10
|
||||
if (date.getDate() < 10) {
|
||||
d = `0${d}`
|
||||
}
|
||||
|
||||
// months *are* zero-indexed, pad if less than 9!
|
||||
if (date.getMonth() < 9) {
|
||||
m = `0${m}`
|
||||
}
|
||||
|
||||
return `${y}-${m}-${d}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare if two dates are equal in terms of day, month, and year
|
||||
*/
|
||||
export function isEqual(a: Date, b: Date): boolean {
|
||||
if (a == null || b == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return isEqualMonth(a, b) && a.getDate() === b.getDate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare if two dates are in the same month of the same year.
|
||||
*/
|
||||
export function isEqualMonth(a: Date, b: Date): boolean {
|
||||
if (a == null || b == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth()
|
||||
}
|
||||
|
||||
export function addDays(date: Date, days: number): Date {
|
||||
var d = new Date(date)
|
||||
d.setDate(d.getDate() + days)
|
||||
return d
|
||||
}
|
||||
|
||||
export function addMonths(date: Date, months: number): Date {
|
||||
const d = new Date(date)
|
||||
d.setMonth(date.getMonth() + months)
|
||||
return d
|
||||
}
|
||||
|
||||
export function addYears(date: Date, years: number): Date {
|
||||
const d = new Date(date)
|
||||
d.setFullYear(date.getFullYear() + years)
|
||||
return d
|
||||
}
|
||||
|
||||
export function startOfWeek(date: Date, firstDayOfWeek: DaysOfWeek = DaysOfWeek.Monday): Date {
|
||||
var d = new Date(date)
|
||||
var day = d.getDay()
|
||||
var diff = (day < firstDayOfWeek ? 7 : 0) + day - firstDayOfWeek
|
||||
|
||||
d.setDate(d.getDate() - diff)
|
||||
return d
|
||||
}
|
||||
|
||||
export function endOfWeek(date: Date, firstDayOfWeek: DaysOfWeek = DaysOfWeek.Monday): Date {
|
||||
var d = new Date(date)
|
||||
var day = d.getDay()
|
||||
var diff = (day < firstDayOfWeek ? -7 : 0) + 6 - (day - firstDayOfWeek)
|
||||
|
||||
d.setDate(d.getDate() + diff)
|
||||
return d
|
||||
}
|
||||
|
||||
export function startOfMonth(date: Date): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth(), 1)
|
||||
}
|
||||
|
||||
export function endOfMonth(date: Date): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth() + 1, 0)
|
||||
}
|
||||
|
||||
export function setMonth(date: Date, month: number): Date {
|
||||
const d = new Date(date)
|
||||
d.setMonth(month)
|
||||
return d
|
||||
}
|
||||
|
||||
export function setYear(date: Date, year: number): Date {
|
||||
const d = new Date(date)
|
||||
d.setFullYear(year)
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is within a min and max
|
||||
*/
|
||||
export function inRange(date: Date, min?: Date, max?: Date): boolean {
|
||||
return clamp(date, min, max) === date
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures date is within range, returns min or max if out of bounds
|
||||
*/
|
||||
export function clamp(date: Date, min?: Date, max?: Date): Date {
|
||||
const time = date.getTime()
|
||||
|
||||
if (min && min instanceof Date && time < min.getTime()) {
|
||||
return min
|
||||
}
|
||||
|
||||
if (max && max instanceof Date && time > max.getTime()) {
|
||||
return max
|
||||
}
|
||||
|
||||
return date
|
||||
}
|
||||
|
||||
/**
|
||||
* given start and end date, return an (inclusive) array of all dates in between
|
||||
* @param start
|
||||
* @param end
|
||||
*/
|
||||
function getDaysInRange(start: Date, end: Date): Date[] {
|
||||
const days: Date[] = []
|
||||
let current = start
|
||||
|
||||
while (!isEqual(current, end)) {
|
||||
days.push(current)
|
||||
current = addDays(current, 1)
|
||||
}
|
||||
|
||||
days.push(current)
|
||||
|
||||
return days
|
||||
}
|
||||
|
||||
/**
|
||||
* given a date, return an array of dates from a calendar perspective
|
||||
* @param date
|
||||
* @param firstDayOfWeek
|
||||
*/
|
||||
export function getViewOfMonth(date: Date, firstDayOfWeek: DaysOfWeek = DaysOfWeek.Monday): Date[] {
|
||||
const start = startOfWeek(startOfMonth(date), firstDayOfWeek)
|
||||
const end = endOfWeek(endOfMonth(date), firstDayOfWeek)
|
||||
|
||||
return getDaysInRange(start, end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Form random hash
|
||||
*/
|
||||
export function chr4() {
|
||||
return Math.random()
|
||||
.toString(16)
|
||||
.slice(-4)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create random identifier with a prefix
|
||||
* @param prefix
|
||||
*/
|
||||
export function createIdentifier(prefix) {
|
||||
return `${prefix}-${chr4()}${chr4()}-${chr4()}-${chr4()}-${chr4()}-${chr4()}${chr4()}${chr4()}`
|
||||
}
|
||||
@ -0,0 +1,878 @@
|
||||
import { createPage } from "../../utils/test-utils"
|
||||
import { E2EElement, E2EPage } from "@stencil/core/testing"
|
||||
import localization from "./date-localization"
|
||||
|
||||
async function getFocusedElement(page: E2EPage) {
|
||||
return page.evaluateHandle(() => document.activeElement)
|
||||
}
|
||||
|
||||
async function getChooseDateButton(page: E2EPage) {
|
||||
return page.find(".duet-date__toggle")
|
||||
}
|
||||
|
||||
async function getInput(page: E2EPage) {
|
||||
return page.find(".duet-date__input")
|
||||
}
|
||||
|
||||
async function getDialog(page: E2EPage) {
|
||||
return page.find(`[role="dialog"]`)
|
||||
}
|
||||
|
||||
async function getGrid(page: E2EPage) {
|
||||
const dialog = await getDialog(page)
|
||||
return dialog.find("table")
|
||||
}
|
||||
|
||||
async function getPicker(page: E2EPage) {
|
||||
return page.find("duet-date-picker")
|
||||
}
|
||||
|
||||
async function setMonthDropdown(page: E2EPage, month: string) {
|
||||
await page.select(".duet-date__select--month", month)
|
||||
await page.waitForChanges()
|
||||
}
|
||||
|
||||
async function setYearDropdown(page: E2EPage, year: string) {
|
||||
await page.select(".duet-date__select--year", year)
|
||||
await page.waitForChanges()
|
||||
}
|
||||
|
||||
async function getPrevMonthButton(page: E2EPage) {
|
||||
const dialog = await getDialog(page)
|
||||
return dialog.find(`.duet-date__prev`)
|
||||
}
|
||||
|
||||
async function getNextMonthButton(page: E2EPage) {
|
||||
const dialog = await getDialog(page)
|
||||
return dialog.find(`.duet-date__next`)
|
||||
}
|
||||
|
||||
async function findByText(context: E2EPage | E2EElement, selector: string, text: string) {
|
||||
const elements = await context.findAll(selector)
|
||||
return elements.find(element => element.innerText.includes(text))
|
||||
}
|
||||
|
||||
async function clickDay(page: E2EPage, date: string) {
|
||||
const grid = await getGrid(page)
|
||||
const button = await findByText(grid, "button", date)
|
||||
await button.click()
|
||||
await page.waitForChanges()
|
||||
}
|
||||
|
||||
async function openCalendar(page: E2EPage) {
|
||||
const button = await getChooseDateButton(page)
|
||||
await button.click()
|
||||
await page.waitForChanges()
|
||||
const dialog = await getDialog(page)
|
||||
await dialog.waitForVisible()
|
||||
}
|
||||
|
||||
async function clickOutside(page: E2EPage) {
|
||||
const input = await getInput(page)
|
||||
await input.click()
|
||||
await page.waitForChanges()
|
||||
const dialog = await getDialog(page)
|
||||
await dialog.waitForNotVisible()
|
||||
}
|
||||
|
||||
async function isCalendarOpen(page: E2EPage): Promise<boolean> {
|
||||
const dialog = await getDialog(page)
|
||||
return dialog.isVisible()
|
||||
}
|
||||
|
||||
async function getYearOptions(page: E2EPage) {
|
||||
return page.$eval(".duet-date__select--year", (select: HTMLSelectElement) => {
|
||||
return Array.from(select.options).map(option => option.value)
|
||||
})
|
||||
}
|
||||
|
||||
const generatePage = (props: Partial<HTMLDuetDatePickerElement> = {}) => {
|
||||
const attrs = Object.entries(props)
|
||||
.map(([attr, value]) => `${attr}="${value}"`)
|
||||
.join(" ")
|
||||
|
||||
return createPage(`
|
||||
<body style="min-height: 400px">
|
||||
<style>
|
||||
:root {
|
||||
--duet-color-primary: #005fcc;
|
||||
--duet-color-text: #333;
|
||||
--duet-color-text-active: #fff;
|
||||
--duet-color-placeholder: #666;
|
||||
--duet-color-button: #f5f5f5;
|
||||
--duet-color-surface: #fff;
|
||||
--duet-color-overlay: rgba(0, 0, 0, 0.8);
|
||||
--duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--duet-font-normal: 400;
|
||||
--duet-font-bold: 600;
|
||||
--duet-radius: 4px;
|
||||
--duet-z-index: 600;
|
||||
}
|
||||
</style>
|
||||
<duet-date-picker ${attrs}></duet-date-picker>
|
||||
</body>
|
||||
`)
|
||||
}
|
||||
|
||||
const ANIMATION_DELAY = 600
|
||||
|
||||
describe("duet-date-picker", () => {
|
||||
it("should render a date picker", async () => {
|
||||
const page = await generatePage()
|
||||
const component = await getPicker(page)
|
||||
expect(component).not.toBeNull()
|
||||
})
|
||||
|
||||
describe("mouse interaction", () => {
|
||||
it("should open on button click", async () => {
|
||||
const page = await generatePage()
|
||||
|
||||
expect(await isCalendarOpen(page)).toBe(false)
|
||||
await openCalendar(page)
|
||||
expect(await isCalendarOpen(page)).toBe(true)
|
||||
})
|
||||
|
||||
it("should close on click outside", async () => {
|
||||
const page = await generatePage()
|
||||
|
||||
await openCalendar(page)
|
||||
expect(await isCalendarOpen(page)).toBe(true)
|
||||
|
||||
await clickOutside(page)
|
||||
expect(await isCalendarOpen(page)).toBe(false)
|
||||
})
|
||||
|
||||
it("supports selecting a date in the future", async () => {
|
||||
const page = await generatePage({ value: "2020-01-01" })
|
||||
await openCalendar(page)
|
||||
|
||||
const picker = await getPicker(page)
|
||||
const nextMonth = await getNextMonthButton(page)
|
||||
const spy = await picker.spyOnEvent("duetChange")
|
||||
|
||||
await nextMonth.click()
|
||||
await nextMonth.click()
|
||||
await nextMonth.click()
|
||||
await clickDay(page, "19 April")
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
expect(spy.lastEvent.detail).toEqual({
|
||||
component: "duet-date-picker",
|
||||
value: "2020-04-19",
|
||||
valueAsDate: new Date(2020, 3, 19).toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
it("supports selecting a date in the past", async () => {
|
||||
const page = await generatePage({ value: "2020-01-01" })
|
||||
await openCalendar(page)
|
||||
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetChange")
|
||||
|
||||
await setMonthDropdown(page, "3")
|
||||
await setYearDropdown(page, "2019")
|
||||
await clickDay(page, "19 April")
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
expect(spy.lastEvent.detail).toEqual({
|
||||
component: "duet-date-picker",
|
||||
value: "2019-04-19",
|
||||
valueAsDate: new Date(2019, 3, 19).toISOString(),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// see: https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/datepicker-dialog.html
|
||||
describe("a11y/ARIA requirements", () => {
|
||||
describe("button", () => {
|
||||
it("has an accessible label", async () => {
|
||||
const page = await generatePage()
|
||||
const button = await getChooseDateButton(page)
|
||||
const element = await button.find(".duet-date__vhidden")
|
||||
expect(element).toEqualText(localization.buttonLabel)
|
||||
})
|
||||
})
|
||||
|
||||
describe("dialog", () => {
|
||||
it("meets a11y requirements", async () => {
|
||||
const page = await generatePage()
|
||||
const dialog = await getDialog(page)
|
||||
|
||||
// has aria-modal attr
|
||||
expect(dialog).toBeDefined()
|
||||
expect(dialog).toEqualAttribute("aria-modal", "true")
|
||||
|
||||
// has accessible label
|
||||
const labelledById = dialog.getAttribute("aria-labelledby")
|
||||
const title = await page.find(`#${labelledById}`)
|
||||
expect(title).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("grid", () => {
|
||||
it("meets a11y requirements", async () => {
|
||||
const page = await generatePage({ value: "2020-01-01" })
|
||||
const grid = await getGrid(page)
|
||||
|
||||
// has accessible label
|
||||
const labelledById = await grid.getAttribute("aria-labelledby")
|
||||
const title = await page.find(`#${labelledById}`)
|
||||
expect(title).toBeDefined()
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
// should be single selected element
|
||||
const selected = await grid.findAll(`[aria-pressed="true"]`)
|
||||
expect(selected.length).toBe(1)
|
||||
|
||||
// only one button is in focus order, has accessible label, and correct text content
|
||||
expect(selected[0]).toEqualAttribute("tabindex", "0")
|
||||
expect(selected[0].innerText).toContain("1 January")
|
||||
})
|
||||
|
||||
it.todo("correctly abbreviates the shortened day names")
|
||||
})
|
||||
|
||||
describe("controls", () => {
|
||||
it.todo("has a label for next month button")
|
||||
it.todo("has a label for previous month button")
|
||||
it.todo("has a label for the month select dropdown")
|
||||
it.todo("has a label for the year select dropdown")
|
||||
})
|
||||
})
|
||||
|
||||
describe("keyboard a11y", () => {
|
||||
it("closes on ESC press", async () => {
|
||||
const page = await generatePage()
|
||||
await openCalendar(page)
|
||||
|
||||
expect(await isCalendarOpen(page)).toBe(true)
|
||||
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
await page.keyboard.press("Escape")
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
|
||||
expect(await isCalendarOpen(page)).toBe(false)
|
||||
})
|
||||
|
||||
it("supports selecting a date in the future", async () => {
|
||||
const page = await generatePage({ value: "2020-01-01" })
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetChange")
|
||||
|
||||
// open calendar
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Enter")
|
||||
await page.waitForChanges()
|
||||
|
||||
// wait for calendar to open
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
|
||||
// set month to april
|
||||
await setMonthDropdown(page, "3")
|
||||
|
||||
// tab to grid
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
|
||||
// tab to grid, select 19th of month
|
||||
await page.keyboard.press("ArrowDown")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowDown")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Enter")
|
||||
await page.waitForChanges()
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
expect(spy.lastEvent.detail).toEqual({
|
||||
component: "duet-date-picker",
|
||||
value: "2020-04-19",
|
||||
valueAsDate: new Date(2020, 3, 19).toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
it("supports selecting a date in the past", async () => {
|
||||
const page = await generatePage({ value: "2020-01-01" })
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetChange")
|
||||
|
||||
// open calendar
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Enter")
|
||||
await page.waitForChanges()
|
||||
|
||||
// wait for calendar to open
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
|
||||
// select april from month dropdown
|
||||
await setMonthDropdown(page, "3")
|
||||
|
||||
// tab to year dropdown, select 2019
|
||||
await page.keyboard.press("Tab")
|
||||
await setYearDropdown(page, "2019")
|
||||
|
||||
// tab to grid
|
||||
await page.keyboard.press("Tab")
|
||||
await page.keyboard.press("Tab")
|
||||
await page.keyboard.press("Tab")
|
||||
|
||||
// select date 19th of month
|
||||
await page.keyboard.press("ArrowDown")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowDown")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Enter")
|
||||
await page.waitForChanges()
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
expect(spy.lastEvent.detail).toEqual({
|
||||
component: "duet-date-picker",
|
||||
value: "2019-04-19",
|
||||
valueAsDate: new Date(2019, 3, 19).toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
it("supports navigating to disabled dates", async () => {
|
||||
const page = await generatePage({ value: "2020-01-01" })
|
||||
|
||||
// disable weekends
|
||||
await page.$eval("duet-date-picker", async (picker: HTMLDuetDatePickerElement) => {
|
||||
picker.isDateDisabled = function isWeekend(date) {
|
||||
return date.getDay() === 0 || date.getDay() === 6
|
||||
}
|
||||
})
|
||||
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetChange")
|
||||
|
||||
// open calendar
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Enter")
|
||||
await page.waitForChanges()
|
||||
|
||||
// wait for calendar to open
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
|
||||
// set month to april
|
||||
await setMonthDropdown(page, "3")
|
||||
|
||||
// tab to grid
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
await page.keyboard.press("Tab")
|
||||
await page.waitForChanges()
|
||||
|
||||
// navigate to 2. april thursday
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
// navigate to 3. april friday
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
// navigate to 4. april saturday
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
|
||||
await page.keyboard.press("Enter")
|
||||
await page.waitForChanges()
|
||||
expect(spy).toHaveReceivedEventTimes(0)
|
||||
|
||||
// navigate to 5. april sunday
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
|
||||
await page.keyboard.press("Enter")
|
||||
await page.waitForChanges()
|
||||
expect(spy).toHaveReceivedEventTimes(0)
|
||||
|
||||
// navigate to 6. april monday
|
||||
await page.keyboard.press("ArrowRight")
|
||||
await page.waitForChanges()
|
||||
|
||||
await page.keyboard.press("Enter")
|
||||
await page.waitForChanges()
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
expect(spy.lastEvent.detail).toEqual({
|
||||
component: "duet-date-picker",
|
||||
value: "2020-04-06",
|
||||
valueAsDate: new Date(2020, 3, 6).toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
it.todo("moves focus to start of week on home press")
|
||||
it.todo("moves focus to end of week end press")
|
||||
|
||||
it.todo("moves focus to previous month on page up press")
|
||||
it.todo("moves focus to next month on page down press")
|
||||
|
||||
it.todo("moves focus to previous year on shift + page down press")
|
||||
it.todo("moves focus to next year on shift + page down press")
|
||||
|
||||
it("maintains curosor position when typing disallowed characters", async () => {
|
||||
const page = await generatePage()
|
||||
const element = await getPicker(page)
|
||||
const input = await getInput(page)
|
||||
const DATE = "2020-03-19"
|
||||
|
||||
// tab to input
|
||||
await page.keyboard.press("Tab")
|
||||
|
||||
// type some _allowed_ chars
|
||||
await page.keyboard.type(DATE, { delay: 50 })
|
||||
|
||||
// move cursor so we can test maintaining position
|
||||
await page.keyboard.press("ArrowLeft")
|
||||
|
||||
// store cursor position
|
||||
const cursorBefore = await input.getProperty("selectionStart")
|
||||
expect(cursorBefore).toBe(DATE.length - 1)
|
||||
|
||||
// attempt to enter _disallowed_ character
|
||||
await page.keyboard.press("a")
|
||||
|
||||
const cursorAfter = await input.getProperty("selectionStart")
|
||||
const value = await element.getProperty("value")
|
||||
|
||||
// we should see cursor hasn't changed
|
||||
expect(cursorAfter).toBe(cursorBefore)
|
||||
|
||||
// and value contains no disallowed chars
|
||||
expect(value).toBe(DATE)
|
||||
})
|
||||
})
|
||||
|
||||
describe("events", () => {
|
||||
it("raises a duetBlur event when the input is blurred", async () => {
|
||||
const page = await generatePage()
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetBlur")
|
||||
|
||||
await page.keyboard.press("Tab")
|
||||
await page.keyboard.press("Tab")
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
})
|
||||
|
||||
it("raises a duetFocus event when the input is focused", async () => {
|
||||
const page = await generatePage()
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetFocus")
|
||||
|
||||
await page.keyboard.press("Tab")
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
})
|
||||
|
||||
it("raises a duetOpen event on open", async () => {
|
||||
const page = await generatePage()
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetOpen")
|
||||
|
||||
await picker.callMethod("show")
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
})
|
||||
|
||||
it("raises a duetClose event on close", async () => {
|
||||
const page = await generatePage()
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetClose")
|
||||
|
||||
await picker.callMethod("hide")
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("focus management", () => {
|
||||
it("traps focus in calendar", async () => {
|
||||
const page = await generatePage()
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
// wait for calendar to open
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
|
||||
// month dropdown
|
||||
let focused = await getFocusedElement(page)
|
||||
let id = await page.evaluate(element => element.id, focused)
|
||||
let label = await page.find(`label[for="${id}"]`)
|
||||
expect(label).toEqualText(localization.monthSelectLabel)
|
||||
|
||||
// year dropdown
|
||||
await page.keyboard.press("Tab")
|
||||
focused = await getFocusedElement(page)
|
||||
id = await page.evaluate(element => element.id, focused)
|
||||
label = await page.find(`label[for="${id}"]`)
|
||||
expect(label).toEqualText(localization.yearSelectLabel)
|
||||
|
||||
// prev month
|
||||
await page.keyboard.press("Tab")
|
||||
focused = await getFocusedElement(page)
|
||||
let ariaLabel = await page.evaluate(element => element.innerText, focused)
|
||||
expect(ariaLabel).toEqual(localization.prevMonthLabel)
|
||||
|
||||
// next month
|
||||
await page.keyboard.press("Tab")
|
||||
focused = await getFocusedElement(page)
|
||||
ariaLabel = await page.evaluate(element => element.innerText, focused)
|
||||
expect(ariaLabel).toBe(localization.nextMonthLabel)
|
||||
|
||||
// day
|
||||
await page.keyboard.press("Tab")
|
||||
focused = await getFocusedElement(page)
|
||||
const tabIndex = await page.evaluate(element => element.tabIndex, focused)
|
||||
expect(tabIndex).toBe(0)
|
||||
|
||||
// close button
|
||||
await page.keyboard.press("Tab")
|
||||
focused = await getFocusedElement(page)
|
||||
ariaLabel = await page.evaluate(element => element.innerText, focused)
|
||||
expect(ariaLabel).toBe(localization.closeLabel)
|
||||
|
||||
// back to month
|
||||
await page.keyboard.press("Tab")
|
||||
focused = await getFocusedElement(page)
|
||||
id = await page.evaluate(element => element.id, focused)
|
||||
label = await page.find(`label[for="${id}"]`)
|
||||
expect(label).toEqualText(localization.monthSelectLabel)
|
||||
})
|
||||
|
||||
it.todo("doesn't shift focus when interacting with calendar navigation controls")
|
||||
it.todo("shifts focus back to button on date select")
|
||||
it.todo("shifts focus back to button on ESC press")
|
||||
it.todo("doesn't shift focus to button on click outside")
|
||||
})
|
||||
|
||||
describe("min/max support", () => {
|
||||
it("supports a min date", async () => {
|
||||
const page = await generatePage({ value: "2020-01-15", min: "2020-01-02" })
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetChange")
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
// wait for calendar to open
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
|
||||
// make sure it's rendered correctly
|
||||
// We use a slightly higher threshold here since the CSS transition
|
||||
// makes certain parts move slightly depending on how the browser converts
|
||||
// the percentage based units into pixels.
|
||||
const screenshot = await page.screenshot()
|
||||
expect(screenshot).toMatchImageSnapshot({
|
||||
failureThreshold: 0.001,
|
||||
failureThresholdType: "percent",
|
||||
})
|
||||
|
||||
// try clicking a day outside the range
|
||||
await clickDay(page, "1 January")
|
||||
expect(spy).toHaveReceivedEventTimes(0)
|
||||
|
||||
// click a day inside the range
|
||||
await clickDay(page, "2 January")
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
expect(spy.lastEvent.detail).toEqual({
|
||||
component: "duet-date-picker",
|
||||
value: "2020-01-02",
|
||||
valueAsDate: new Date(2020, 0, 2).toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
it("supports a max date", async () => {
|
||||
const page = await generatePage({ value: "2020-01-15", max: "2020-01-30" })
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetChange")
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
// wait for calendar to open
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
|
||||
// make sure it's rendered correctly
|
||||
// We use a slightly higher threshold here since the CSS transition
|
||||
// makes certain parts move slightly depending on how the browser converts
|
||||
// the percentage based units into pixels.
|
||||
const screenshot = await page.screenshot()
|
||||
expect(screenshot).toMatchImageSnapshot({
|
||||
failureThreshold: 0.001,
|
||||
failureThresholdType: "percent",
|
||||
})
|
||||
|
||||
// try clicking a day outside the range
|
||||
await clickDay(page, "31 January")
|
||||
expect(spy).toHaveReceivedEventTimes(0)
|
||||
|
||||
// click a day inside the range
|
||||
await clickDay(page, "30 January")
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
expect(spy.lastEvent.detail).toEqual({
|
||||
component: "duet-date-picker",
|
||||
value: "2020-01-30",
|
||||
valueAsDate: new Date(2020, 0, 30).toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
it("supports min and max dates", async () => {
|
||||
const page = await generatePage({ value: "2020-01-15", min: "2020-01-02", max: "2020-01-30" })
|
||||
const picker = await getPicker(page)
|
||||
const spy = await picker.spyOnEvent("duetChange")
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
// wait for calendar to open
|
||||
await page.waitFor(ANIMATION_DELAY)
|
||||
|
||||
// make sure it's rendered correctly.
|
||||
// We use a slightly higher threshold here since the CSS transition
|
||||
// makes certain parts move slightly depending on how the browser converts
|
||||
// the percentage based units into pixels.
|
||||
const screenshot = await page.screenshot()
|
||||
expect(screenshot).toMatchImageSnapshot({
|
||||
failureThreshold: 0.001,
|
||||
failureThresholdType: "percent",
|
||||
})
|
||||
|
||||
// try clicking a day less than min
|
||||
await clickDay(page, "1 January")
|
||||
expect(spy).toHaveReceivedEventTimes(0)
|
||||
|
||||
// try clicking a day greater than max
|
||||
await clickDay(page, "31 January")
|
||||
expect(spy).toHaveReceivedEventTimes(0)
|
||||
|
||||
// click a day inside the range
|
||||
await clickDay(page, "30 January")
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
expect(spy.lastEvent.detail).toEqual({
|
||||
component: "duet-date-picker",
|
||||
value: "2020-01-30",
|
||||
valueAsDate: new Date(2020, 0, 30).toISOString(),
|
||||
})
|
||||
})
|
||||
|
||||
it("disables prev month button if same month and year as min", async () => {
|
||||
const page = await generatePage({ value: "2020-04-19", min: "2020-04-01" })
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
const prevMonthButton = await getPrevMonthButton(page)
|
||||
expect(prevMonthButton).toHaveAttribute("disabled")
|
||||
})
|
||||
|
||||
it("disables next month button if same month and year as max", async () => {
|
||||
const page = await generatePage({ value: "2020-04-19", max: "2020-04-30" })
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
const nextMonthButton = await getNextMonthButton(page)
|
||||
expect(nextMonthButton).toHaveAttribute("disabled")
|
||||
})
|
||||
|
||||
it("does not disable prev/next buttons when only month value (but not year) is same as min and max", async () => {
|
||||
// there was a bug whereby both buttons would be disabled if the min/max/selected date
|
||||
// had the same month (here: 4), but different years. this tests ensures no regression
|
||||
const page = await generatePage({ value: "2020-04-19", min: "2019-04-19", max: "2021-04-19" })
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
const prevMonthButton = await getPrevMonthButton(page)
|
||||
const nextMonthButton = await getNextMonthButton(page)
|
||||
|
||||
expect(prevMonthButton).not.toHaveAttribute("disabled")
|
||||
expect(nextMonthButton).not.toHaveAttribute("disabled")
|
||||
})
|
||||
|
||||
it("respects min/max dates when generating year dropdown", async () => {
|
||||
const page = await generatePage({ value: "2020-04-19", min: "2019-04-19", max: "2021-04-19" })
|
||||
const picker = await getPicker(page)
|
||||
|
||||
// range smaller than default 40 year range
|
||||
let options = await getYearOptions(page)
|
||||
expect(options).toEqual(["2019", "2020", "2021"])
|
||||
|
||||
// range larger than default 40 year range
|
||||
const minYear = 1990
|
||||
const maxYear = 2050
|
||||
picker.setAttribute("min", `${minYear}-01-02`)
|
||||
picker.setAttribute("max", `${maxYear}-01-30`)
|
||||
await page.waitForChanges()
|
||||
|
||||
options = await getYearOptions(page)
|
||||
|
||||
expect(options.length).toBe(maxYear - minYear + 1)
|
||||
expect(options[0]).toBe(minYear.toString())
|
||||
expect(options[options.length - 1]).toBe(maxYear.toString())
|
||||
})
|
||||
|
||||
it("respects min/max dates when generating month dropdown", async () => {
|
||||
const page = await generatePage({ value: "2020-04-19", min: "2019-04-01", max: "2020-05-31" })
|
||||
|
||||
await openCalendar(page)
|
||||
|
||||
function getAllowedMonths() {
|
||||
return page.$eval(".duet-date__select--month", (select: HTMLSelectElement) => {
|
||||
return Array.from(select.options)
|
||||
.filter(option => !option.disabled)
|
||||
.map(option => option.value)
|
||||
})
|
||||
}
|
||||
|
||||
// in 2020, January - May is allowed
|
||||
let allowedMonths = await getAllowedMonths()
|
||||
expect(allowedMonths).toEqual(["0", "1", "2", "3", "4"])
|
||||
|
||||
await setYearDropdown(page, "2019")
|
||||
|
||||
// in 2019, April - December is allowed
|
||||
allowedMonths = await getAllowedMonths()
|
||||
expect(allowedMonths).toEqual(["3", "4", "5", "6", "7", "8", "9", "10", "11"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("methods", () => {
|
||||
it("should open calendar on show()", async () => {
|
||||
const page = await generatePage()
|
||||
const picker = await getPicker(page)
|
||||
|
||||
expect(await isCalendarOpen(page)).toBe(false)
|
||||
|
||||
await picker.callMethod("show")
|
||||
await page.waitForChanges()
|
||||
|
||||
expect(await isCalendarOpen(page)).toBe(true)
|
||||
})
|
||||
|
||||
it("should close calendar on hide()", async () => {
|
||||
const page = await generatePage()
|
||||
const picker = await getPicker(page)
|
||||
|
||||
await picker.callMethod("show")
|
||||
await page.waitForChanges()
|
||||
expect(await isCalendarOpen(page)).toBe(true)
|
||||
|
||||
await picker.callMethod("hide")
|
||||
await page.waitForChanges()
|
||||
|
||||
const dialog = await getDialog(page)
|
||||
await dialog.waitForNotVisible()
|
||||
|
||||
expect(await isCalendarOpen(page)).toBe(false)
|
||||
})
|
||||
|
||||
it("should focus input on setFocus()", async () => {
|
||||
const page = await generatePage()
|
||||
const picker = await getPicker(page)
|
||||
|
||||
await picker.callMethod("setFocus")
|
||||
await page.waitForChanges()
|
||||
|
||||
const focused = await getFocusedElement(page)
|
||||
const tagName = await page.evaluate(element => element.tagName, focused)
|
||||
|
||||
expect(tagName.toLowerCase()).toEqualText("input")
|
||||
})
|
||||
})
|
||||
|
||||
describe("form interaction", () => {
|
||||
it("supports required attribute", async () => {
|
||||
const page = await createPage(`
|
||||
<form>
|
||||
<duet-date-picker required></duet-date-picker>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
`)
|
||||
|
||||
const picker = await getPicker(page)
|
||||
const form = await page.find("form")
|
||||
const button = await page.find("button[type='submit']")
|
||||
const spy = await form.spyOnEvent("submit")
|
||||
|
||||
await button.click()
|
||||
await page.waitForChanges()
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(0)
|
||||
|
||||
picker.setProperty("value", "2020-01-01")
|
||||
await page.waitForChanges()
|
||||
await button.click()
|
||||
|
||||
expect(spy).toHaveReceivedEventTimes(1)
|
||||
})
|
||||
|
||||
it("always submits value as ISO date", async () => {
|
||||
const page = await createPage(`
|
||||
<form>
|
||||
<duet-date-picker name="test"></duet-date-picker>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
`)
|
||||
|
||||
const picker = await getPicker(page)
|
||||
const input = await getInput(page)
|
||||
|
||||
// use non-ISO date format
|
||||
await page.$eval("duet-date-picker", async (picker: HTMLDuetDatePickerElement) => {
|
||||
var DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
|
||||
|
||||
picker.dateAdapter = {
|
||||
parse(value = "", createDate) {
|
||||
const matches = value.match(DATE_FORMAT)
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format(date) {
|
||||
return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
picker.setProperty("value", "2020-01-01")
|
||||
await page.waitForChanges()
|
||||
|
||||
// submitted value should be ISO format
|
||||
const submittedValue = await page.$eval("form", (form: HTMLFormElement) => new FormData(form).get("test"))
|
||||
expect(submittedValue).toEqual("2020-01-01")
|
||||
|
||||
// whilst the displayed value should be Finnish format
|
||||
expect(await input.getProperty("value")).toEqual("1.1.2020")
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,533 @@
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date *,
|
||||
.duet-date *::before,
|
||||
.duet-date *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.duet-date {
|
||||
box-sizing: border-box;
|
||||
color: var(--duet-color-text);
|
||||
display: block;
|
||||
font-family: var(--duet-font);
|
||||
margin: 0;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ INPUT
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__input {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: var(--duet-color-surface);
|
||||
border: 1px solid var(--duet-color-border, var(--duet-color-text)); // for backwards compatibility, fallback to old value
|
||||
border-radius: var(--duet-radius);
|
||||
color: var(--duet-color-text);
|
||||
float: none;
|
||||
font-family: var(--duet-font);
|
||||
font-size: 100%;
|
||||
line-height: normal;
|
||||
padding: 14px 60px 14px 14px;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--duet-color-primary);
|
||||
box-shadow: 0 0 0 1px var(--duet-color-primary);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: var(--duet-color-placeholder);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:-moz-placeholder {
|
||||
color: var(--duet-color-placeholder);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:-ms-input-placeholder {
|
||||
color: var(--duet-color-placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__input-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ TOGGLE
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__toggle {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-webkit-user-select: none;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: var(--duet-color-button);
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
border-bottom-right-radius: var(--duet-radius);
|
||||
border-top-right-radius: var(--duet-radius);
|
||||
box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
color: var(--duet-color-text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: calc(100% - 2px);
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
user-select: none;
|
||||
width: 48px;
|
||||
z-index: 2;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px var(--duet-color-primary);
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__toggle-icon {
|
||||
display: flex;
|
||||
flex-basis: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ DIALOG
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__dialog {
|
||||
display: flex;
|
||||
left: 0;
|
||||
min-width: 320px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
transform: scale(0.96) translateZ(0) translateY(-20px);
|
||||
transform-origin: top right;
|
||||
transition: transform 300ms ease, opacity 300ms ease, visibility 300ms ease;
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
will-change: transform, opacity, visibility;
|
||||
z-index: var(--duet-z-index);
|
||||
|
||||
@media (max-width: 35.9375em) {
|
||||
background: var(--duet-color-overlay);
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: translateZ(0);
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
||||
&.is-left {
|
||||
left: auto;
|
||||
right: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
// The value of 1.0001 fixes a Chrome glitch with scaling
|
||||
transform: scale(1.0001) translateZ(0) translateY(0);
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__dialog-content {
|
||||
background: var(--duet-color-surface);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--duet-radius);
|
||||
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.1);
|
||||
margin-left: auto;
|
||||
margin-top: 8px;
|
||||
max-width: 310px;
|
||||
min-width: 290px;
|
||||
padding: 16px 16px 20px;
|
||||
position: relative;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
z-index: var(--duet-z-index);
|
||||
|
||||
@media (max-width: 35.9375em) {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
border-top-left-radius: var(--duet-radius);
|
||||
border-top-right-radius: var(--duet-radius);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
max-width: none;
|
||||
min-height: 26em;
|
||||
opacity: 0;
|
||||
padding: 0 8% 20px;
|
||||
position: absolute;
|
||||
transform: translateZ(0) translateY(100%);
|
||||
transition: transform 400ms ease, opacity 400ms ease, visibility 400ms ease;
|
||||
visibility: hidden;
|
||||
will-change: transform, opacity, visibility;
|
||||
|
||||
.is-active & {
|
||||
opacity: 1;
|
||||
transform: translateZ(0) translateY(0);
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ TABLE
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
color: var(--duet-color-text);
|
||||
font-size: 1rem;
|
||||
font-weight: var(--duet-font-normal);
|
||||
line-height: 1.25;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.duet-date__table-header {
|
||||
font-size: 0.75rem;
|
||||
font-weight: var(--duet-font-bold);
|
||||
letter-spacing: 1px;
|
||||
line-height: 1.25;
|
||||
padding-bottom: 8px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.duet-date__cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.duet-date__day {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--duet-color-text);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-family: var(--duet-font);
|
||||
font-size: 0.875rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: var(--duet-font-normal);
|
||||
height: 36px;
|
||||
line-height: 1.25;
|
||||
padding: 0 0 1px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 36px;
|
||||
z-index: 1;
|
||||
|
||||
&.is-today {
|
||||
box-shadow: 0 0 0 1px var(--duet-color-primary);
|
||||
position: relative;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
&:hover::before,
|
||||
&.is-today::before {
|
||||
background: var(--duet-color-primary);
|
||||
border-radius: 50%;
|
||||
bottom: 0;
|
||||
content: "";
|
||||
left: 0;
|
||||
opacity: 0.06;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&[aria-pressed="true"],
|
||||
&:focus {
|
||||
background: var(--duet-color-primary);
|
||||
box-shadow: none;
|
||||
color: var(--duet-color-text-active);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--duet-color-primary);
|
||||
box-shadow: 0 0 5px var(--duet-color-primary);
|
||||
color: var(--duet-color-text-active);
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 5px var(--duet-color-primary);
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
&:not(.is-month) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:not(.is-month),
|
||||
&[aria-disabled="true"] {
|
||||
background: transparent;
|
||||
color: var(--duet-color-text);
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&[aria-disabled="true"] {
|
||||
&.is-today {
|
||||
box-shadow: 0 0 0 1px var(--duet-color-primary);
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 5px var(--duet-color-primary);
|
||||
background: var(--duet-color-primary);
|
||||
color: var(--duet-color-text-active);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-today)::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-outside {
|
||||
background: var(--duet-color-button);
|
||||
box-shadow: none;
|
||||
color: var(--duet-color-text);
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ HEADER
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ NAVIGATION
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__nav {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.duet-date__prev,
|
||||
.duet-date__next {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: var(--duet-color-button);
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--duet-color-text);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
padding: 0;
|
||||
transition: background-color 300ms ease;
|
||||
width: 32px;
|
||||
|
||||
@media (max-width: 35.9375em) {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px var(--duet-color-primary);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:active:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ SELECT
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__select {
|
||||
display: inline-flex;
|
||||
margin-top: 4px;
|
||||
position: relative;
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
select {
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
|
||||
&:focus + .duet-date__select-label {
|
||||
box-shadow: 0 0 0 2px var(--duet-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__select-label {
|
||||
align-items: center;
|
||||
border-radius: var(--duet-radius);
|
||||
color: var(--duet-color-text);
|
||||
display: flex;
|
||||
font-size: 1.25rem;
|
||||
font-weight: var(--duet-font-bold);
|
||||
line-height: 1.25;
|
||||
padding: 0 4px 0 8px;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ MOBILE
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__mobile {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
margin-left: -10%;
|
||||
overflow: hidden;
|
||||
padding: 12px 20px;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 120%;
|
||||
|
||||
@media (min-width: 36em) {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: -8px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.duet-date__mobile-heading {
|
||||
display: inline-block;
|
||||
font-weight: var(--duet-font-bold);
|
||||
max-width: 84%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
@media (min-width: 36em) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ CLOSE
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__close {
|
||||
-webkit-appearance: none;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: var(--duet-color-button);
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--duet-color-text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
|
||||
@media (min-width: 36em) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px var(--duet-color-primary);
|
||||
outline: none;
|
||||
|
||||
@media (min-width: 36em) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// DUET DATE PICKER __ VISUALLY HIDDEN
|
||||
// ---------------------------------------------
|
||||
|
||||
.duet-date__vhidden {
|
||||
border: 0;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
}
|
||||
@ -0,0 +1,796 @@
|
||||
import {
|
||||
Component,
|
||||
ComponentInterface,
|
||||
Host,
|
||||
Prop,
|
||||
Element,
|
||||
h,
|
||||
Event,
|
||||
EventEmitter,
|
||||
State,
|
||||
Listen,
|
||||
Method,
|
||||
Watch,
|
||||
} from "@stencil/core"
|
||||
import {
|
||||
addDays,
|
||||
startOfWeek,
|
||||
endOfWeek,
|
||||
setMonth,
|
||||
setYear,
|
||||
clamp,
|
||||
inRange,
|
||||
endOfMonth,
|
||||
startOfMonth,
|
||||
printISODate,
|
||||
parseISODate,
|
||||
createIdentifier,
|
||||
DaysOfWeek,
|
||||
createDate,
|
||||
} from "./date-utils"
|
||||
import { DatePickerInput } from "./date-picker-input"
|
||||
import { DatePickerMonth } from "./date-picker-month"
|
||||
import defaultLocalization, { DuetLocalizedText } from "./date-localization"
|
||||
import isoAdapter, { DuetDateAdapter } from "./date-adapter"
|
||||
|
||||
function range(from: number, to: number) {
|
||||
var result: number[] = []
|
||||
for (var i = from; i <= to; i++) {
|
||||
result.push(i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const keyCode = {
|
||||
TAB: 9,
|
||||
ESC: 27,
|
||||
SPACE: 32,
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
END: 35,
|
||||
HOME: 36,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
}
|
||||
|
||||
function cleanValue(input: HTMLInputElement, regex: RegExp): string {
|
||||
const value = input.value
|
||||
const cursor = input.selectionStart
|
||||
|
||||
const beforeCursor = value.slice(0, cursor)
|
||||
const afterCursor = value.slice(cursor, value.length)
|
||||
|
||||
const filteredBeforeCursor = beforeCursor.replace(regex, "")
|
||||
const filterAfterCursor = afterCursor.replace(regex, "")
|
||||
|
||||
const newValue = filteredBeforeCursor + filterAfterCursor
|
||||
const newCursor = filteredBeforeCursor.length
|
||||
|
||||
input.value = newValue
|
||||
input.selectionStart = input.selectionEnd = newCursor
|
||||
|
||||
return newValue
|
||||
}
|
||||
|
||||
export type DuetDatePickerChangeEvent = {
|
||||
component: "duet-date-picker"
|
||||
valueAsDate: Date
|
||||
value: string
|
||||
}
|
||||
export type DuetDatePickerFocusEvent = {
|
||||
component: "duet-date-picker"
|
||||
}
|
||||
export type DuetDatePickerOpenEvent = {
|
||||
component: "duet-date-picker"
|
||||
}
|
||||
export type DuetDatePickerCloseEvent = {
|
||||
component: "duet-date-picker"
|
||||
}
|
||||
export type DuetDatePickerDirection = "left" | "right"
|
||||
|
||||
const DISALLOWED_CHARACTERS = /[^0-9\.\/\-]+/g
|
||||
const TRANSITION_MS = 300
|
||||
|
||||
export type DateDisabledPredicate = (date: Date) => boolean
|
||||
|
||||
@Component({
|
||||
tag: "duet-date-picker",
|
||||
styleUrl: "duet-date-picker.scss",
|
||||
shadow: false,
|
||||
scoped: false,
|
||||
})
|
||||
export class DuetDatePicker implements ComponentInterface {
|
||||
/**
|
||||
* Own Properties
|
||||
*/
|
||||
private monthSelectId = createIdentifier("DuetDateMonth")
|
||||
private yearSelectId = createIdentifier("DuetDateYear")
|
||||
private dialogLabelId = createIdentifier("DuetDateLabel")
|
||||
|
||||
private datePickerButton: HTMLButtonElement
|
||||
private datePickerInput: HTMLInputElement
|
||||
private firstFocusableElement: HTMLElement
|
||||
private monthSelectNode: HTMLElement
|
||||
private dialogWrapperNode: HTMLElement
|
||||
private focusedDayNode: HTMLButtonElement
|
||||
|
||||
private focusTimeoutId: ReturnType<typeof setTimeout>
|
||||
|
||||
private initialTouchX: number = null
|
||||
private initialTouchY: number = null
|
||||
|
||||
/**
|
||||
* Whilst dateAdapter is used for handling the formatting/parsing dates in the input,
|
||||
* these are used to format dates exclusively for the benefit of screen readers.
|
||||
*
|
||||
* We prefer DateTimeFormat over date.toLocaleDateString, as the former has
|
||||
* better performance when formatting large number of dates. See:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString#Performance
|
||||
*/
|
||||
private dateFormatShort: Intl.DateTimeFormat
|
||||
private dateFormatLong: Intl.DateTimeFormat
|
||||
|
||||
/**
|
||||
* Reference to host HTML element.
|
||||
*/
|
||||
@Element() element: HTMLElement
|
||||
|
||||
/**
|
||||
* State() variables
|
||||
*/
|
||||
@State() activeFocus = false
|
||||
@State() focusedDay = new Date()
|
||||
@State() open = false
|
||||
|
||||
/**
|
||||
* Public Property API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Name of the date picker input.
|
||||
*/
|
||||
@Prop() name: string = "date"
|
||||
|
||||
/**
|
||||
* Adds a unique identifier for the date picker input. Use this instead of html `id` attribute.
|
||||
*/
|
||||
@Prop() identifier: string = ""
|
||||
|
||||
/**
|
||||
* Makes the date picker input component disabled. This prevents users from being able to
|
||||
* interact with the input, and conveys its inactive state to assistive technologies.
|
||||
*/
|
||||
@Prop({ reflect: true }) disabled: boolean = false
|
||||
|
||||
/**
|
||||
* Defines a specific role attribute for the date picker input.
|
||||
*/
|
||||
@Prop() role: string
|
||||
|
||||
/**
|
||||
* Forces the opening direction of the calendar modal to be always left or right.
|
||||
* This setting can be useful when the input is smaller than the opening date picker
|
||||
* would be as by default the picker always opens towards right.
|
||||
*/
|
||||
@Prop() direction: DuetDatePickerDirection = "right"
|
||||
|
||||
/**
|
||||
* Should the input be marked as required?
|
||||
*/
|
||||
@Prop() required: boolean = false
|
||||
|
||||
/**
|
||||
* Date value. Must be in IS0-8601 format: YYYY-MM-DD.
|
||||
*/
|
||||
@Prop({ reflect: true, mutable: true }) value: string = ""
|
||||
|
||||
/**
|
||||
* Minimum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD.
|
||||
* This setting can be used alone or together with the max property.
|
||||
*/
|
||||
@Prop() min: string = ""
|
||||
|
||||
/**
|
||||
* Maximum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD.
|
||||
* This setting can be used alone or together with the min property.
|
||||
*/
|
||||
@Prop() max: string = ""
|
||||
|
||||
/**
|
||||
* Which day is considered first day of the week? `0` for Sunday, `1` for Monday, etc.
|
||||
* Default is Monday.
|
||||
*/
|
||||
@Prop() firstDayOfWeek: DaysOfWeek = DaysOfWeek.Monday
|
||||
|
||||
/**
|
||||
* Button labels, day names, month names, etc, used for localization.
|
||||
* Default is English.
|
||||
*/
|
||||
@Prop() localization: DuetLocalizedText = defaultLocalization
|
||||
|
||||
/**
|
||||
* Date adapter, for custom parsing/formatting.
|
||||
* Must be object with a `parse` function which accepts a `string` and returns a `Date`,
|
||||
* and a `format` function which accepts a `Date` and returns a `string`.
|
||||
* Default is IS0-8601 parsing and formatting.
|
||||
*/
|
||||
@Prop() dateAdapter: DuetDateAdapter = isoAdapter
|
||||
|
||||
/**
|
||||
* Controls which days are disabled and therefore disallowed.
|
||||
* For example, this can be used to disallow selection of weekends.
|
||||
*/
|
||||
@Prop() isDateDisabled: DateDisabledPredicate = () => false
|
||||
|
||||
/**
|
||||
* Events section.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Event emitted when a date is selected.
|
||||
*/
|
||||
@Event() duetChange: EventEmitter<DuetDatePickerChangeEvent>
|
||||
|
||||
/**
|
||||
* Event emitted the date picker input is blurred.
|
||||
*/
|
||||
@Event() duetBlur: EventEmitter<DuetDatePickerFocusEvent>
|
||||
|
||||
/**
|
||||
* Event emitted the date picker input is focused.
|
||||
*/
|
||||
@Event() duetFocus: EventEmitter<DuetDatePickerFocusEvent>
|
||||
|
||||
/**
|
||||
* Event emitted the date picker modal is opened.
|
||||
*/
|
||||
@Event() duetOpen: EventEmitter<DuetDatePickerOpenEvent>
|
||||
|
||||
/**
|
||||
* Event emitted the date picker modal is closed.
|
||||
*/
|
||||
@Event() duetClose: EventEmitter<DuetDatePickerCloseEvent>
|
||||
|
||||
connectedCallback() {
|
||||
this.createDateFormatters()
|
||||
}
|
||||
|
||||
@Watch("localization")
|
||||
createDateFormatters() {
|
||||
this.dateFormatShort = new Intl.DateTimeFormat(this.localization.locale, { day: "numeric", month: "long" })
|
||||
this.dateFormatLong = new Intl.DateTimeFormat(this.localization.locale, {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Component event handling.
|
||||
*/
|
||||
@Listen("click", { target: "document", capture: true })
|
||||
handleDocumentClick(e: MouseEvent) {
|
||||
if (!this.open) {
|
||||
return
|
||||
}
|
||||
|
||||
// the dialog and the button aren't considered clicks outside.
|
||||
// dialog for obvious reasons, but the button needs to be skipped
|
||||
// so that two things are possible:
|
||||
//
|
||||
// a) clicking again on the button when dialog is open should close the modal.
|
||||
// without skipping the button here, we would see a click outside
|
||||
// _and_ a click on the button, so the `open` state goes
|
||||
// open -> close (click outside) -> open (click button)
|
||||
//
|
||||
// b) clicking another date picker's button should close the current calendar
|
||||
// and open the new one. this means we can't stopPropagation() on the button itself
|
||||
//
|
||||
// this was the only satisfactory combination of things to get the above to work
|
||||
|
||||
const isClickOutside = e
|
||||
.composedPath()
|
||||
.every(node => node !== this.dialogWrapperNode && node !== this.datePickerButton)
|
||||
|
||||
if (isClickOutside) {
|
||||
this.hide(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public methods API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets focus on the date picker's input. Use this method instead of the global `focus()`.
|
||||
*/
|
||||
@Method() async setFocus() {
|
||||
return this.datePickerInput.focus()
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the calendar modal, moving focus to the calendar inside.
|
||||
*/
|
||||
@Method() async show() {
|
||||
this.open = true
|
||||
this.duetOpen.emit({
|
||||
component: "duet-date-picker",
|
||||
})
|
||||
this.setFocusedDay(parseISODate(this.value) || new Date())
|
||||
|
||||
clearTimeout(this.focusTimeoutId)
|
||||
this.focusTimeoutId = setTimeout(() => this.monthSelectNode.focus(), TRANSITION_MS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the calendar modal. Set `moveFocusToButton` to false to prevent focus
|
||||
* returning to the date picker's button. Default is true.
|
||||
*/
|
||||
@Method() async hide(moveFocusToButton = true) {
|
||||
this.open = false
|
||||
this.duetClose.emit({
|
||||
component: "duet-date-picker",
|
||||
})
|
||||
|
||||
// in cases where calendar is quickly shown and hidden
|
||||
// we should avoid moving focus to the button
|
||||
clearTimeout(this.focusTimeoutId)
|
||||
|
||||
if (moveFocusToButton) {
|
||||
// iOS VoiceOver needs to wait for all transitions to finish.
|
||||
setTimeout(() => this.datePickerButton.focus(), TRANSITION_MS + 200)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Local methods.
|
||||
*/
|
||||
private enableActiveFocus = () => {
|
||||
this.activeFocus = true
|
||||
}
|
||||
|
||||
private disableActiveFocus = () => {
|
||||
this.activeFocus = false
|
||||
}
|
||||
|
||||
private addDays(days: number) {
|
||||
this.setFocusedDay(addDays(this.focusedDay, days))
|
||||
}
|
||||
|
||||
private addMonths(months: number) {
|
||||
this.setMonth(this.focusedDay.getMonth() + months)
|
||||
}
|
||||
|
||||
private addYears(years: number) {
|
||||
this.setYear(this.focusedDay.getFullYear() + years)
|
||||
}
|
||||
|
||||
private startOfWeek() {
|
||||
this.setFocusedDay(startOfWeek(this.focusedDay, this.firstDayOfWeek))
|
||||
}
|
||||
|
||||
private endOfWeek() {
|
||||
this.setFocusedDay(endOfWeek(this.focusedDay, this.firstDayOfWeek))
|
||||
}
|
||||
|
||||
private setMonth(month: number) {
|
||||
const min = setMonth(startOfMonth(this.focusedDay), month)
|
||||
const max = endOfMonth(min)
|
||||
const date = setMonth(this.focusedDay, month)
|
||||
|
||||
this.setFocusedDay(clamp(date, min, max))
|
||||
}
|
||||
|
||||
private setYear(year: number) {
|
||||
const min = setYear(startOfMonth(this.focusedDay), year)
|
||||
const max = endOfMonth(min)
|
||||
const date = setYear(this.focusedDay, year)
|
||||
|
||||
this.setFocusedDay(clamp(date, min, max))
|
||||
}
|
||||
|
||||
private setFocusedDay(day: Date) {
|
||||
this.focusedDay = clamp(day, parseISODate(this.min), parseISODate(this.max))
|
||||
}
|
||||
|
||||
private toggleOpen = (e: Event) => {
|
||||
e.preventDefault()
|
||||
this.open ? this.hide(false) : this.show()
|
||||
}
|
||||
|
||||
private handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.keyCode === keyCode.ESC) {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private handleBlur = (event: Event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
this.duetBlur.emit({
|
||||
component: "duet-date-picker",
|
||||
})
|
||||
}
|
||||
|
||||
private handleFocus = (event: Event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
this.duetFocus.emit({
|
||||
component: "duet-date-picker",
|
||||
})
|
||||
}
|
||||
|
||||
private handleTouchStart = (event: TouchEvent) => {
|
||||
const touch = event.changedTouches[0]
|
||||
this.initialTouchX = touch.pageX
|
||||
this.initialTouchY = touch.pageY
|
||||
}
|
||||
|
||||
private handleTouchMove = (event: TouchEvent) => {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
private handleTouchEnd = (event: TouchEvent) => {
|
||||
const touch = event.changedTouches[0]
|
||||
const distX = touch.pageX - this.initialTouchX // get horizontal dist traveled
|
||||
const distY = touch.pageY - this.initialTouchY // get vertical dist traveled
|
||||
const threshold = 70
|
||||
|
||||
const isHorizontalSwipe = Math.abs(distX) >= threshold && Math.abs(distY) <= threshold
|
||||
const isDownwardsSwipe = Math.abs(distY) >= threshold && Math.abs(distX) <= threshold && distY > 0
|
||||
|
||||
if (isHorizontalSwipe) {
|
||||
this.addMonths(distX < 0 ? 1 : -1)
|
||||
} else if (isDownwardsSwipe) {
|
||||
this.hide(false)
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
this.initialTouchY = null
|
||||
this.initialTouchX = null
|
||||
}
|
||||
|
||||
private handleNextMonthClick = (event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
this.addMonths(1)
|
||||
}
|
||||
|
||||
private handlePreviousMonthClick = (event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
this.addMonths(-1)
|
||||
}
|
||||
|
||||
private handleFirstFocusableKeydown = (event: KeyboardEvent) => {
|
||||
// this ensures focus is trapped inside the dialog
|
||||
if (event.keyCode === keyCode.TAB && event.shiftKey) {
|
||||
this.focusedDayNode.focus()
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyboardNavigation = (event: KeyboardEvent) => {
|
||||
// handle tab separately, since it needs to be treated
|
||||
// differently to other keyboard interactions
|
||||
if (event.keyCode === keyCode.TAB && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
this.firstFocusableElement.focus()
|
||||
return
|
||||
}
|
||||
|
||||
var handled = true
|
||||
|
||||
switch (event.keyCode) {
|
||||
case keyCode.RIGHT:
|
||||
this.addDays(1)
|
||||
break
|
||||
case keyCode.LEFT:
|
||||
this.addDays(-1)
|
||||
break
|
||||
case keyCode.DOWN:
|
||||
this.addDays(7)
|
||||
break
|
||||
case keyCode.UP:
|
||||
this.addDays(-7)
|
||||
break
|
||||
case keyCode.PAGE_UP:
|
||||
if (event.shiftKey) {
|
||||
this.addYears(-1)
|
||||
} else {
|
||||
this.addMonths(-1)
|
||||
}
|
||||
break
|
||||
case keyCode.PAGE_DOWN:
|
||||
if (event.shiftKey) {
|
||||
this.addYears(1)
|
||||
} else {
|
||||
this.addMonths(1)
|
||||
}
|
||||
break
|
||||
case keyCode.HOME:
|
||||
this.startOfWeek()
|
||||
break
|
||||
case keyCode.END:
|
||||
this.endOfWeek()
|
||||
break
|
||||
default:
|
||||
handled = false
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
event.preventDefault()
|
||||
this.enableActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
private handleDaySelect = (_event: MouseEvent, day: Date) => {
|
||||
const isInRange = inRange(day, parseISODate(this.min), parseISODate(this.max))
|
||||
const isAllowed = !this.isDateDisabled(day)
|
||||
|
||||
if (isInRange && isAllowed) {
|
||||
this.setValue(day)
|
||||
this.hide()
|
||||
} else {
|
||||
// for consistency we should set the focused day in cases where
|
||||
// user has selected a day that has been specifically disallowed
|
||||
this.setFocusedDay(day)
|
||||
}
|
||||
}
|
||||
|
||||
private handleMonthSelect = e => {
|
||||
this.setMonth(parseInt(e.target.value, 10))
|
||||
}
|
||||
|
||||
private handleYearSelect = e => {
|
||||
this.setYear(parseInt(e.target.value, 10))
|
||||
}
|
||||
|
||||
private handleInputChange = () => {
|
||||
const target = this.datePickerInput
|
||||
|
||||
// clean up any invalid characters
|
||||
cleanValue(target, DISALLOWED_CHARACTERS)
|
||||
|
||||
const parsed = this.dateAdapter.parse(target.value, createDate)
|
||||
if (parsed || target.value === "") {
|
||||
this.setValue(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
private setValue(date: Date) {
|
||||
this.value = printISODate(date)
|
||||
this.duetChange.emit({
|
||||
component: "duet-date-picker",
|
||||
value: this.value,
|
||||
valueAsDate: date,
|
||||
})
|
||||
}
|
||||
|
||||
private processFocusedDayNode = (element: HTMLButtonElement) => {
|
||||
this.focusedDayNode = element
|
||||
|
||||
if (this.activeFocus && this.open) {
|
||||
setTimeout(() => element.focus(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* render() function
|
||||
* Always the last one in the class.
|
||||
*/
|
||||
render() {
|
||||
const valueAsDate = parseISODate(this.value)
|
||||
const formattedDate = valueAsDate && this.dateAdapter.format(valueAsDate)
|
||||
const selectedYear = (valueAsDate || this.focusedDay).getFullYear()
|
||||
const focusedMonth = this.focusedDay.getMonth()
|
||||
const focusedYear = this.focusedDay.getFullYear()
|
||||
|
||||
const minDate = parseISODate(this.min)
|
||||
const maxDate = parseISODate(this.max)
|
||||
const prevMonthDisabled =
|
||||
minDate != null && minDate.getMonth() === focusedMonth && minDate.getFullYear() === focusedYear
|
||||
const nextMonthDisabled =
|
||||
maxDate != null && maxDate.getMonth() === focusedMonth && maxDate.getFullYear() === focusedYear
|
||||
|
||||
const minYear = minDate ? minDate.getFullYear() : selectedYear - 10
|
||||
const maxYear = maxDate ? maxDate.getFullYear() : selectedYear + 10
|
||||
|
||||
return (
|
||||
<Host>
|
||||
<div class="duet-date">
|
||||
<DatePickerInput
|
||||
dateFormatter={this.dateFormatLong}
|
||||
value={this.value}
|
||||
valueAsDate={valueAsDate}
|
||||
formattedValue={formattedDate}
|
||||
onInput={this.handleInputChange}
|
||||
onBlur={this.handleBlur}
|
||||
onFocus={this.handleFocus}
|
||||
onClick={this.toggleOpen}
|
||||
name={this.name}
|
||||
disabled={this.disabled}
|
||||
role={this.role}
|
||||
required={this.required}
|
||||
identifier={this.identifier}
|
||||
localization={this.localization}
|
||||
buttonRef={element => (this.datePickerButton = element)}
|
||||
inputRef={element => (this.datePickerInput = element)}
|
||||
/>
|
||||
|
||||
<div
|
||||
class={{
|
||||
"duet-date__dialog": true,
|
||||
"is-left": this.direction === "left",
|
||||
"is-active": this.open,
|
||||
}}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-hidden={this.open ? "false" : "true"}
|
||||
aria-labelledby={this.dialogLabelId}
|
||||
onTouchMove={this.handleTouchMove}
|
||||
onTouchStart={this.handleTouchStart}
|
||||
onTouchEnd={this.handleTouchEnd}
|
||||
>
|
||||
<div
|
||||
class="duet-date__dialog-content"
|
||||
onKeyDown={this.handleEscKey}
|
||||
ref={element => (this.dialogWrapperNode = element)}
|
||||
>
|
||||
<div class="duet-date__mobile" onFocusin={this.disableActiveFocus}>
|
||||
<label class="duet-date__mobile-heading">{this.localization.calendarHeading}</label>
|
||||
<button
|
||||
class="duet-date__close"
|
||||
ref={element => (this.firstFocusableElement = element)}
|
||||
onKeyDown={this.handleFirstFocusableKeydown}
|
||||
onClick={() => this.hide()}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M18.3 5.71c-.39-.39-1.02-.39-1.41 0L12 10.59 7.11 5.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L10.59 12 5.7 16.89c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 13.41l4.89 4.89c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z" />
|
||||
</svg>
|
||||
<span class="duet-date__vhidden">{this.localization.closeLabel}</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* @ts-ignore */}
|
||||
<div class="duet-date__header" onFocusin={this.disableActiveFocus}>
|
||||
<div>
|
||||
<h2 id={this.dialogLabelId} class="duet-date__vhidden" aria-live="polite" aria-atomic="true">
|
||||
{this.localization.monthNames[focusedMonth]} {this.focusedDay.getFullYear()}
|
||||
</h2>
|
||||
|
||||
<label htmlFor={this.monthSelectId} class="duet-date__vhidden">
|
||||
{this.localization.monthSelectLabel}
|
||||
</label>
|
||||
<div class="duet-date__select">
|
||||
<select
|
||||
id={this.monthSelectId}
|
||||
class="duet-date__select--month"
|
||||
ref={element => (this.monthSelectNode = element)}
|
||||
onChange={this.handleMonthSelect}
|
||||
>
|
||||
{this.localization.monthNames.map((month, i) => (
|
||||
<option
|
||||
key={month}
|
||||
value={i}
|
||||
selected={i === focusedMonth}
|
||||
disabled={
|
||||
!inRange(
|
||||
new Date(focusedYear, i, 1),
|
||||
minDate ? startOfMonth(minDate) : null,
|
||||
maxDate ? endOfMonth(maxDate) : null
|
||||
)
|
||||
}
|
||||
>
|
||||
{month}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div class="duet-date__select-label" aria-hidden="true">
|
||||
<span>{this.localization.monthNamesShort[focusedMonth]}</span>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M8.12 9.29L12 13.17l3.88-3.88c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0L6.7 10.7c-.39-.39-.39-1.02 0-1.41.39-.38 1.03-.39 1.42 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label htmlFor={this.yearSelectId} class="duet-date__vhidden">
|
||||
{this.localization.yearSelectLabel}
|
||||
</label>
|
||||
<div class="duet-date__select">
|
||||
<select id={this.yearSelectId} class="duet-date__select--year" onChange={this.handleYearSelect}>
|
||||
{range(minYear, maxYear).map(year => (
|
||||
<option key={year} selected={year === focusedYear}>
|
||||
{year}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div class="duet-date__select-label" aria-hidden="true">
|
||||
<span>{this.focusedDay.getFullYear()}</span>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M8.12 9.29L12 13.17l3.88-3.88c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0L6.7 10.7c-.39-.39-.39-1.02 0-1.41.39-.38 1.03-.39 1.42 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="duet-date__nav">
|
||||
<button
|
||||
class="duet-date__prev"
|
||||
onClick={this.handlePreviousMonthClick}
|
||||
disabled={prevMonthDisabled}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="21"
|
||||
height="21"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M14.71 15.88L10.83 12l3.88-3.88c.39-.39.39-1.02 0-1.41-.39-.39-1.02-.39-1.41 0L8.71 11.3c-.39.39-.39 1.02 0 1.41l4.59 4.59c.39.39 1.02.39 1.41 0 .38-.39.39-1.03 0-1.42z" />
|
||||
</svg>
|
||||
<span class="duet-date__vhidden">{this.localization.prevMonthLabel}</span>
|
||||
</button>
|
||||
<button
|
||||
class="duet-date__next"
|
||||
onClick={this.handleNextMonthClick}
|
||||
disabled={nextMonthDisabled}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="21"
|
||||
height="21"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M9.29 15.88L13.17 12 9.29 8.12c-.39-.39-.39-1.02 0-1.41.39-.39 1.02-.39 1.41 0l4.59 4.59c.39.39.39 1.02 0 1.41L10.7 17.3c-.39.39-1.02.39-1.41 0-.38-.39-.39-1.03 0-1.42z" />
|
||||
</svg>
|
||||
<span class="duet-date__vhidden">{this.localization.nextMonthLabel}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<DatePickerMonth
|
||||
dateFormatter={this.dateFormatShort}
|
||||
selectedDate={valueAsDate}
|
||||
focusedDate={this.focusedDay}
|
||||
onDateSelect={this.handleDaySelect}
|
||||
onKeyboardNavigation={this.handleKeyboardNavigation}
|
||||
labelledById={this.dialogLabelId}
|
||||
localization={this.localization}
|
||||
firstDayOfWeek={this.firstDayOfWeek}
|
||||
focusedDayRef={this.processFocusedDayNode}
|
||||
min={minDate}
|
||||
max={maxDate}
|
||||
isDateDisabled={this.isDateDisabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Host>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
# duet-date-picker
|
||||
|
||||
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ---------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| `dateAdapter` | -- | Date adapter, for custom parsing/formatting. Must be object with a `parse` function which accepts a `string` and returns a `Date`, and a `format` function which accepts a `Date` and returns a `string`. Default is IS0-8601 parsing and formatting. | `DuetDateAdapter` | `isoAdapter` |
|
||||
| `direction` | `direction` | Forces the opening direction of the calendar modal to be always left or right. This setting can be useful when the input is smaller than the opening date picker would be as by default the picker always opens towards right. | `"left" \| "right"` | `"right"` |
|
||||
| `disabled` | `disabled` | Makes the date picker input component disabled. This prevents users from being able to interact with the input, and conveys its inactive state to assistive technologies. | `boolean` | `false` |
|
||||
| `firstDayOfWeek` | `first-day-of-week` | Which day is considered first day of the week? `0` for Sunday, `1` for Monday, etc. Default is Monday. | `DaysOfWeek.Friday \| DaysOfWeek.Monday \| DaysOfWeek.Saturday \| DaysOfWeek.Sunday \| DaysOfWeek.Thursday \| DaysOfWeek.Tuesday \| DaysOfWeek.Wednesday` | `DaysOfWeek.Monday` |
|
||||
| `identifier` | `identifier` | Adds a unique identifier for the date picker input. Use this instead of html `id` attribute. | `string` | `""` |
|
||||
| `isDateDisabled` | -- | Controls which days are disabled and therefore disallowed. For example, this can be used to disallow selection of weekends. | `(date: Date) => boolean` | `() => false` |
|
||||
| `localization` | -- | Button labels, day names, month names, etc, used for localization. Default is English. | `{ buttonLabel: string; placeholder: string; selectedDateMessage: string; prevMonthLabel: string; nextMonthLabel: string; monthSelectLabel: string; yearSelectLabel: string; closeLabel: string; calendarHeading: string; dayNames: DayNames; monthNames: MonthsNames; monthNamesShort: MonthsNames; locale: string \| string[]; }` | `defaultLocalization` |
|
||||
| `max` | `max` | Maximum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD. This setting can be used alone or together with the min property. | `string` | `""` |
|
||||
| `min` | `min` | Minimum date allowed to be picked. Must be in IS0-8601 format: YYYY-MM-DD. This setting can be used alone or together with the max property. | `string` | `""` |
|
||||
| `name` | `name` | Name of the date picker input. | `string` | `"date"` |
|
||||
| `required` | `required` | Should the input be marked as required? | `boolean` | `false` |
|
||||
| `role` | `role` | Defines a specific role attribute for the date picker input. | `string` | `undefined` |
|
||||
| `value` | `value` | Date value. Must be in IS0-8601 format: YYYY-MM-DD. | `string` | `""` |
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Description | Type |
|
||||
| ------------ | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `duetBlur` | Event emitted the date picker input is blurred. | `CustomEvent<{ component: "duet-date-picker"; }>` |
|
||||
| `duetChange` | Event emitted when a date is selected. | `CustomEvent<{ component: "duet-date-picker"; valueAsDate: Date; value: string; }>` |
|
||||
| `duetClose` | Event emitted the date picker modal is closed. | `CustomEvent<{ component: "duet-date-picker"; }>` |
|
||||
| `duetFocus` | Event emitted the date picker input is focused. | `CustomEvent<{ component: "duet-date-picker"; }>` |
|
||||
| `duetOpen` | Event emitted the date picker modal is opened. | `CustomEvent<{ component: "duet-date-picker"; }>` |
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
### `hide(moveFocusToButton?: boolean) => Promise<void>`
|
||||
|
||||
Hide the calendar modal. Set `moveFocusToButton` to false to prevent focus
|
||||
returning to the date picker's button. Default is true.
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `Promise<void>`
|
||||
|
||||
|
||||
|
||||
### `setFocus() => Promise<void>`
|
||||
|
||||
Sets focus on the date picker's input. Use this method instead of the global `focus()`.
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `Promise<void>`
|
||||
|
||||
|
||||
|
||||
### `show() => Promise<void>`
|
||||
|
||||
Show the calendar modal, moving focus to the calendar inside.
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `Promise<void>`
|
||||
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
*Built with [StencilJS](https://stenciljs.com/)*
|
||||
@ -0,0 +1,564 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>Duet Date Picker examples</title>
|
||||
<link rel="preload" href="/build/duet.esm.js" as="script" crossorigin="anonymous" />
|
||||
<link rel="preload" href="/build/duet-date-picker.entry.js" as="script" crossorigin="anonymous" />
|
||||
<link rel="preload" href="/themes/default.css" as="style" />
|
||||
<script type="module" src="/build/duet.esm.js"></script>
|
||||
<script nomodule src="/build/duet.js"></script>
|
||||
<link rel="stylesheet" href="/themes/default.css" id="theme" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/styles/obsidian.min.css"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/highlight.min.js"></script>
|
||||
<script>
|
||||
document.documentElement.className += " js"
|
||||
if (hljs) {
|
||||
hljs.initHighlightingOnLoad()
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* ---------------------------------------------
|
||||
* BELOW STYLES ARE JUST FOR THE EXAMPLE,
|
||||
* YOU CAN REMOVE AND REPLACE THEM.
|
||||
* ------------------------------------------ */
|
||||
|
||||
body {
|
||||
font-weight: var(--duet-font-normal);
|
||||
font-family: var(--duet-font);
|
||||
color: var(--duet-color-text);
|
||||
background: var(--duet-color-surface);
|
||||
line-height: 1.5;
|
||||
margin: 0 auto;
|
||||
max-width: 40rem;
|
||||
font-size: 100%;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.js main {
|
||||
transition: opacity 300ms 80ms ease;
|
||||
visibility: hidden;
|
||||
will-change: opacity;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.js.hydrated main {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.js.hydrated .loader {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.js .loader,
|
||||
.js .loader:after {
|
||||
border-radius: 50%;
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
}
|
||||
|
||||
.js .loader {
|
||||
transition: opacity 300ms 80ms ease;
|
||||
top: calc(50% - 2.25rem);
|
||||
left: calc(50% - 2.25rem);
|
||||
font-size: 10px;
|
||||
position: fixed;
|
||||
text-indent: -9999rem;
|
||||
border-top: 0.5rem solid rgba(0, 0, 0, 0.1);
|
||||
border-right: 0.5rem solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 0.5rem solid rgba(0, 0, 0, 0.1);
|
||||
border-left: 0.5rem solid var(--duet-color-primary);
|
||||
transform: translateZ(0);
|
||||
animation: load 0.7s infinite linear;
|
||||
}
|
||||
@keyframes load {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: var(--duet-font-bold);
|
||||
font-size: 2rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: var(--duet-font-bold);
|
||||
font-size: 1.5rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--duet-color-primary);
|
||||
}
|
||||
|
||||
output {
|
||||
border-radius: var(--duet-radius);
|
||||
background: var(--duet-color-button);
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 0.6rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
pre {
|
||||
line-height: 1.5;
|
||||
font-size: 0.875rem;
|
||||
white-space: pre;
|
||||
display: block;
|
||||
}
|
||||
|
||||
pre code,
|
||||
code.hljs {
|
||||
border-radius: var(--duet-radius);
|
||||
font-weight: var(--duet-font-normal);
|
||||
background: #282b2e;
|
||||
color: #fff;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
span.hljs-name {
|
||||
font-weight: var(--duet-font-normal);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: monaco, Consolas, monospace, monospace;
|
||||
background: var(--duet-color-button);
|
||||
margin: 0;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.button,
|
||||
.theme {
|
||||
background: var(--duet-color-primary);
|
||||
color: var(--duet-color-text-active);
|
||||
border-radius: var(--duet-radius);
|
||||
font-weight: var(--duet-font-bold);
|
||||
font-size: 1rem;
|
||||
-webkit-appearance: none;
|
||||
padding: 0.75rem 1rem;
|
||||
margin: 0 0 0.5rem;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.duet-date {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Duet Date Picker examples</h1>
|
||||
<p>
|
||||
Duet Date Picker is an open source version of
|
||||
<a href="https://www.duetds.com">Duet Design System’s</a> accessible date picker. It can be implemented and used
|
||||
across any JavaScript framework or no framework at all.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>
|
||||
For documentation, please see the
|
||||
<a href="https://github.com/duetds/date-picker">GitHub repository</a>.
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<button class="theme">Switch theme</button>
|
||||
<script>
|
||||
var stylesheet = document.getElementById("theme")
|
||||
var theme = document.querySelector(".theme")
|
||||
|
||||
theme.addEventListener("click", function() {
|
||||
if (!theme.classList.contains("active")) {
|
||||
stylesheet.setAttribute("href", "/themes/dark.css")
|
||||
document.documentElement.classList.add("dark-theme")
|
||||
theme.classList.add("active")
|
||||
} else {
|
||||
stylesheet.setAttribute("href", "/themes/default.css")
|
||||
document.documentElement.classList.remove("dark-theme")
|
||||
theme.classList.remove("active")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<h2>Default</h2>
|
||||
<label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker></code></pre>
|
||||
|
||||
<h2>Using show() method</h2>
|
||||
<label for="date2">Choose a date</label>
|
||||
<duet-date-picker class="cal" identifier="date2"></duet-date-picker>
|
||||
<button type="button" class="button show">Show date picker</button>
|
||||
<script>
|
||||
var button = document.querySelector(".show")
|
||||
|
||||
button.addEventListener("click", function() {
|
||||
document.querySelector(".cal").show()
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
<button type="button">Show date picker</button>
|
||||
|
||||
<script>
|
||||
const button = document.querySelector("button")
|
||||
|
||||
button.addEventListener("click", function() {
|
||||
document.querySelector("duet-date-picker").show()
|
||||
});
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Using setFocus() method</h2>
|
||||
<label for="date3">Choose a date</label>
|
||||
<duet-date-picker class="cal2" identifier="date3"></duet-date-picker>
|
||||
<button type="button" class="button focus">Focus date picker</button>
|
||||
<script>
|
||||
var button = document.querySelector(".focus")
|
||||
|
||||
button.addEventListener("click", function() {
|
||||
document.querySelector(".cal2").setFocus()
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
<button type="button">Focus date picker</button>
|
||||
|
||||
<script>
|
||||
const button = document.querySelector("button")
|
||||
|
||||
button.addEventListener("click", function() {
|
||||
document.querySelector("duet-date-picker").setFocus()
|
||||
});
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Getting selected value</h2>
|
||||
<label for="date4">Choose a date</label>
|
||||
<duet-date-picker class="picker" identifier="date4"></duet-date-picker>
|
||||
<output>undefined</output>
|
||||
<script>
|
||||
var picker = document.querySelector(".picker")
|
||||
var output = document.querySelector("output")
|
||||
|
||||
picker.addEventListener("duetChange", function(event) {
|
||||
output.innerHTML = event.detail.valueAsDate
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
<output>undefined</output>
|
||||
|
||||
<script>
|
||||
const picker = document.querySelector("duet-date-picker")
|
||||
const output = document.querySelector("output")
|
||||
|
||||
picker.addEventListener("duetChange", function(event) {
|
||||
output.innerHTML = event.detail.valueAsDate
|
||||
});
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Predefined value</h2>
|
||||
<label for="date5">Choose a date</label>
|
||||
<duet-date-picker identifier="date5" value="2020-06-16"></duet-date-picker>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date" value="2020-06-16">
|
||||
</duet-date-picker></code></pre>
|
||||
|
||||
<h2>Minimum and maximum date</h2>
|
||||
<label for="date6">Choose a date</label>
|
||||
<duet-date-picker identifier="date6" min="1990-06-10" max="2020-07-18" value="2020-06-16"></duet-date-picker>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date" min="1990-06-10"
|
||||
max="2020-07-18" value="2020-06-16">
|
||||
</duet-date-picker></code></pre>
|
||||
|
||||
<h2>Localization</h2>
|
||||
<label for="date7" lang="fi">Valitse päivämäärä</label>
|
||||
<duet-date-picker class="picker-fi" lang="fi" identifier="date7"></duet-date-picker>
|
||||
<script>
|
||||
var pickerFinnish = document.querySelector(".picker-fi")
|
||||
var DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
|
||||
|
||||
pickerFinnish.dateAdapter = {
|
||||
parse: function parse() {
|
||||
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
|
||||
var createDate = arguments.length > 1 ? arguments[1] : undefined
|
||||
var matches = value.match(DATE_FORMAT)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format: function format(date) {
|
||||
return ""
|
||||
.concat(date.getDate(), ".")
|
||||
.concat(date.getMonth() + 1, ".")
|
||||
.concat(date.getFullYear())
|
||||
},
|
||||
}
|
||||
|
||||
pickerFinnish.localization = {
|
||||
buttonLabel: "Valitse päivämäärä",
|
||||
placeholder: "pp.kk.vvvv",
|
||||
selectedDateMessage: "Valittu päivämäärä on",
|
||||
prevMonthLabel: "Edellinen kuukausi",
|
||||
nextMonthLabel: "Seuraava kuukausi",
|
||||
monthSelectLabel: "Kuukausi",
|
||||
yearSelectLabel: "Vuosi",
|
||||
closeLabel: "Sulje ikkuna",
|
||||
calendarHeading: "Valitse päivämäärä",
|
||||
dayNames: ["Sunnuntai", "Maanantai", "Tiistai", "Keskiviikko", "Torstai", "Perjantai", "Lauantai"],
|
||||
monthNames: [
|
||||
"Tammikuu",
|
||||
"Helmikuu",
|
||||
"Maaliskuu",
|
||||
"Huhtikuu",
|
||||
"Toukokuu",
|
||||
"Kesäkuu",
|
||||
"Heinäkuu",
|
||||
"Elokuu",
|
||||
"Syyskuu",
|
||||
"Lokakuu",
|
||||
"Marraskuu",
|
||||
"Joulukuu",
|
||||
],
|
||||
monthNamesShort: [
|
||||
"Tammi",
|
||||
"Helmi",
|
||||
"Maalis",
|
||||
"Huhti",
|
||||
"Touko",
|
||||
"Kesä",
|
||||
"Heinä",
|
||||
"Elo",
|
||||
"Syys",
|
||||
"Loka",
|
||||
"Marras",
|
||||
"Joulu",
|
||||
],
|
||||
locale: "fi-FI",
|
||||
}
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Valitse päivämäärä</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
const picker = document.querySelector("duet-date-picker")
|
||||
const DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
|
||||
|
||||
picker.dateAdapter = {
|
||||
parse(value = "", createDate) {
|
||||
const matches = value.match(DATE_FORMAT)
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format(date) {
|
||||
return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`
|
||||
},
|
||||
}
|
||||
|
||||
picker.localization = {
|
||||
buttonLabel: "Valitse päivämäärä",
|
||||
placeholder: "pp.kk.vvvv",
|
||||
selectedDateMessage: "Valittu päivämäärä on",
|
||||
prevMonthLabel: "Edellinen kuukausi",
|
||||
nextMonthLabel: "Seuraava kuukausi",
|
||||
monthSelectLabel: "Kuukausi",
|
||||
yearSelectLabel: "Vuosi",
|
||||
closeLabel: "Sulje ikkuna",
|
||||
calendarHeading: "Valitse päivämäärä",
|
||||
dayNames: ["Sunnuntai", "Maanantai", "Tiistai", "Keskiviikko", "Torstai", "Perjantai", "Lauantai"],
|
||||
monthNames: ["Tammikuu", "Helmikuu", "Maaliskuu", "Huhtikuu", "Toukokuu", "Kesäkuu", "Heinäkuu", "Elokuu", "Syyskuu", "Lokakuu", "Marraskuu", "Joulukuu"],
|
||||
monthNamesShort: ["Tammi", "Helmi", "Maalis", "Huhti", "Touko", "Kesä", "Heinä", "Elo", "Syys", "Loka", "Marras", "Joulu"],
|
||||
locale: "fi-FI",
|
||||
}
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Changing first day of week and date format</h2>
|
||||
<label for="date8">Choose a date</label>
|
||||
<duet-date-picker first-day-of-week="0" class="picker-week" identifier="date8"></duet-date-picker>
|
||||
<script>
|
||||
const pickerWeek = document.querySelector(".picker-week")
|
||||
const DATE_FORMAT_US = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/
|
||||
|
||||
pickerWeek.dateAdapter = {
|
||||
parse: function parse() {
|
||||
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
|
||||
var createDate = arguments.length > 1 ? arguments[1] : undefined
|
||||
var matches = value.match(DATE_FORMAT_US)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[1], matches[2])
|
||||
}
|
||||
},
|
||||
format: function format(date) {
|
||||
return ""
|
||||
.concat(date.getMonth() + 1, "/")
|
||||
.concat(date.getDate(), "/")
|
||||
.concat(date.getFullYear())
|
||||
},
|
||||
}
|
||||
|
||||
pickerWeek.localization = {
|
||||
buttonLabel: "Choose date",
|
||||
placeholder: "mm/dd/yyyy",
|
||||
selectedDateMessage: "Selected date is",
|
||||
prevMonthLabel: "Previous month",
|
||||
nextMonthLabel: "Next month",
|
||||
monthSelectLabel: "Month",
|
||||
yearSelectLabel: "Year",
|
||||
closeLabel: "Close window",
|
||||
calendarHeading: "Choose a date",
|
||||
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
monthNames: [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
locale: "en-US",
|
||||
}
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker first-day-of-week="0" identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
const picker = document.querySelector("duet-date-picker")
|
||||
const DATE_FORMAT_US = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/
|
||||
|
||||
picker.dateAdapter = {
|
||||
parse(value = "", createDate) {
|
||||
const matches = value.match(DATE_FORMAT_US)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[1], matches[2])
|
||||
}
|
||||
},
|
||||
format(date) {
|
||||
return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`
|
||||
},
|
||||
}
|
||||
|
||||
picker.localization = {
|
||||
buttonLabel: "Choose date",
|
||||
placeholder: "mm/dd/yyyy",
|
||||
selectedDateMessage: "Selected date is",
|
||||
prevMonthLabel: "Previous month",
|
||||
nextMonthLabel: "Next month",
|
||||
monthSelectLabel: "Month",
|
||||
yearSelectLabel: "Year",
|
||||
closeLabel: "Close window",
|
||||
calendarHeading: "Choose a date",
|
||||
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
||||
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
locale: "en-US",
|
||||
}
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Required atrribute</h2>
|
||||
<label for="date9">Choose a date (required)</label>
|
||||
<form class="form-picker-required">
|
||||
<duet-date-picker required identifier="date9"></duet-date-picker>
|
||||
<button type="submit" class="button">Submit form</button>
|
||||
</form>
|
||||
<script>
|
||||
const form = document.querySelector(".form-picker-required")
|
||||
form.addEventListener("submit", function(e) {
|
||||
e.preventDefault()
|
||||
alert("Submitted")
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker required identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector(".form-picker-required")
|
||||
form.addEventListener("submit", function(e) {
|
||||
e.preventDefault()
|
||||
alert("Submitted")
|
||||
})
|
||||
</script></code></pre>
|
||||
|
||||
<h2>Disable selectable days</h2>
|
||||
<p>
|
||||
This only disables selection of dates in the popup calendar. You must still handle the case where a user enters
|
||||
a disallowed date into the input.
|
||||
</p>
|
||||
<label for="dateDisabledWeekend">Choose a date</label>
|
||||
<duet-date-picker class="picker-disabled-weekend" identifier="dateDisabledWeekend"></duet-date-picker>
|
||||
<script>
|
||||
function isWeekend(date) {
|
||||
return date.getDay() === 0 || date.getDay() === 6
|
||||
}
|
||||
|
||||
const pickerDisableWeekend = document.querySelector(".picker-disabled-weekend")
|
||||
pickerDisableWeekend.isDateDisabled = isWeekend
|
||||
|
||||
pickerDisableWeekend.addEventListener("duetChange", function(e) {
|
||||
if (isWeekend(e.detail.valueAsDate)) {
|
||||
alert("Please select a weekday")
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<pre><code class="html"><label for="date">Choose a date</label>
|
||||
<duet-date-picker identifier="date"></duet-date-picker>
|
||||
|
||||
<script>
|
||||
function isWeekend(date) {
|
||||
return date.getDay() === 0 || date.getDay() === 6
|
||||
}
|
||||
|
||||
const pickerDisableWeekend = document.querySelector(".picker-disabled-weekend")
|
||||
pickerDisableWeekend.isDateDisabled = isWeekend
|
||||
|
||||
pickerDisableWeekend.addEventListener("duetChange", function(e) {
|
||||
if (isWeekend(e.detail.valueAsDate)) {
|
||||
alert("Please select a weekday")
|
||||
}
|
||||
})
|
||||
</script></code></pre>
|
||||
<br />
|
||||
<p>
|
||||
© 2020 LocalTapiola Services Ltd /
|
||||
<a href="https://www.duetds.com">Duet Design System</a>.<br />Licensed under the MIT license.
|
||||
</p>
|
||||
</main>
|
||||
<div class="loader">Loading…</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,17 @@
|
||||
:root {
|
||||
--duet-color-primary: #005fcc;
|
||||
--duet-color-text: #fff;
|
||||
--duet-color-text-active: #fff;
|
||||
--duet-color-placeholder: #aaa;
|
||||
--duet-color-button: #444;
|
||||
--duet-color-surface: #222;
|
||||
--duet-color-overlay: rgba(0, 0, 0, 0.8);
|
||||
--duet-color-border: #fff;
|
||||
|
||||
--duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--duet-font-normal: 400;
|
||||
--duet-font-bold: 600;
|
||||
|
||||
--duet-radius: 4px;
|
||||
--duet-z-index: 600;
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
:root {
|
||||
--duet-color-primary: #005fcc;
|
||||
--duet-color-text: #333;
|
||||
--duet-color-text-active: #fff;
|
||||
--duet-color-placeholder: #666;
|
||||
--duet-color-button: #f5f5f5;
|
||||
--duet-color-surface: #fff;
|
||||
--duet-color-overlay: rgba(0, 0, 0, 0.8);
|
||||
--duet-color-border: #333;
|
||||
|
||||
--duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--duet-font-normal: 400;
|
||||
--duet-font-bold: 600;
|
||||
|
||||
--duet-radius: 4px;
|
||||
--duet-z-index: 600;
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import { newE2EPage, E2EPage } from "@stencil/core/testing"
|
||||
import { Page as PuppeteerPage } from "puppeteer"
|
||||
|
||||
export type DuetE2EPage = E2EPage & Pick<PuppeteerPage, "screenshot" | "viewport">
|
||||
|
||||
type DuetE2EPageOptions = { html: string; viewportWidth: number }
|
||||
|
||||
export async function createPage(optionsOrHtml?: string | DuetE2EPageOptions) {
|
||||
const options: DuetE2EPageOptions =
|
||||
typeof optionsOrHtml === "string" ? { html: optionsOrHtml, viewportWidth: 600 } : optionsOrHtml
|
||||
|
||||
const page = (await newE2EPage()) as DuetE2EPage
|
||||
const viewport = Object.assign({ height: page.viewport().height }, { width: options.viewportWidth })
|
||||
await page.setViewport(viewport)
|
||||
await page.setContent(options.html, { waitUntil: "networkidle0" })
|
||||
await page.evaluateHandle(() => (document as any).fonts.ready)
|
||||
|
||||
// monkey patch screenshot function to add some extra features
|
||||
const screenshot = page.screenshot
|
||||
page.screenshot = async function() {
|
||||
// get the element's height, and set viewport to that height
|
||||
// this enables us to get full page, clipped screenshots
|
||||
const htmlElement = await page.$("html")
|
||||
const { width, height } = await htmlElement.boundingBox()
|
||||
await page.setViewport({ width: page.viewport().width, height: Math.round(height) })
|
||||
|
||||
return screenshot.call(page, {
|
||||
clip: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: Math.round(width),
|
||||
height: Math.round(height),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return page
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
import { Config } from "@stencil/core"
|
||||
import { sass } from "@stencil/sass"
|
||||
|
||||
export const config: Config = {
|
||||
// See https://github.com/ionic-team/stencil/blob/master/src/declarations/config.ts for config
|
||||
namespace: "duet",
|
||||
enableCache: true,
|
||||
hashFileNames: false,
|
||||
autoprefixCss: false,
|
||||
minifyCss: true,
|
||||
buildEs5: true,
|
||||
taskQueue: "immediate",
|
||||
preamble: "Built with Duet Design System",
|
||||
hashedFileNameLength: 8,
|
||||
commonjs: { include: /node_modules|(..\/.+)/ } as any,
|
||||
bundles: [{ components: ["duet-date-picker"] }],
|
||||
devServer: {
|
||||
openBrowser: true,
|
||||
port: 3333,
|
||||
reloadStrategy: "pageReload",
|
||||
},
|
||||
extras: {
|
||||
// We need the following for IE11 and old Edge:
|
||||
cssVarsShim: true,
|
||||
dynamicImportShim: true,
|
||||
// We don’t use shadow DOM so this is not needed:
|
||||
shadowDomShim: false,
|
||||
// Setting the below option to “true” will actually break Safari 10 support:
|
||||
safari10: false,
|
||||
// This is to tackle an Angular specific performance issue:
|
||||
initializeNextTick: true,
|
||||
// Don’t need any of these so setting them to “false”:
|
||||
scriptDataOpts: false,
|
||||
appendChildSlotFix: false,
|
||||
cloneNodeFix: false,
|
||||
slotChildNodesFix: false,
|
||||
},
|
||||
outputTargets: [
|
||||
{
|
||||
type: "dist-hydrate-script",
|
||||
dir: "hydrate",
|
||||
empty: false,
|
||||
},
|
||||
{
|
||||
type: "dist-custom-elements-bundle",
|
||||
dir: "custom-element",
|
||||
empty: true,
|
||||
},
|
||||
{
|
||||
type: "dist",
|
||||
dir: "dist",
|
||||
empty: true,
|
||||
copy: [{ src: "themes", warn: true }],
|
||||
},
|
||||
{
|
||||
type: "docs-readme",
|
||||
},
|
||||
{
|
||||
type: "www",
|
||||
dir: "www",
|
||||
serviceWorker: null,
|
||||
empty: true,
|
||||
baseUrl: "https://duetds.github.io/",
|
||||
prerenderConfig: "./prerender.config.ts",
|
||||
copy: [{ src: "themes", dest: "themes", warn: true }],
|
||||
},
|
||||
],
|
||||
plugins: [sass()],
|
||||
testing: {
|
||||
browserHeadless: process.env.TEST_HEADLESS !== "false",
|
||||
setupFilesAfterEnv: ["<rootDir>/jest/jest-setup.js"],
|
||||
testPathIgnorePatterns: ["<rootDir>/hydrate/", "<rootDir>/dist/"],
|
||||
},
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": false,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["dom", "es2017"],
|
||||
"moduleResolution": "node",
|
||||
"module": "esnext",
|
||||
"target": "es2017",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h"
|
||||
},
|
||||
"include": ["src", "types/jsx.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@ -1,371 +1,463 @@
|
||||
<div class="skip_menu">
|
||||
<a href="#sub" class="contGo" title="본문 바로가기">본문 바로가기</a>
|
||||
</div>
|
||||
<div class="skip_menu">
|
||||
<a href="#sub" class="contGo" title="본문 바로가기">본문 바로가기</a>
|
||||
</div>
|
||||
|
||||
<header class="header">
|
||||
<div class="inner pc_header">
|
||||
<h1 class="logo"><a href="./index.html" title="메인으로 이동"><img src="/kofair_case_seed/usr/images/layout/header_logo.png" alt="한국공정거래조정원 KOREA FAIR TRADE MEDIATION AGENCY"></a></h1>
|
||||
<div class="util_bar">
|
||||
<div class="search_area">
|
||||
<input type="text" class="input_text input_search" title="검색어 입력">
|
||||
<button type="button" class="btn btn_icon btn_search" title="검색버튼"><i class="icon search"></i></button>
|
||||
</div>
|
||||
<div class="util_btn_area">
|
||||
<button type="button" class="btn btn_text btn_40 blue_fill"><i class="icon sertification"></i>본인인증</button>
|
||||
<button type="button" class="btn btn_text btn_40 blue_fill hide"><i class="icon sertification_out"></i>인증해제</button>
|
||||
<button type="button" class="btn btn_text btn_40 gray_border only_icon" title="페이지 확대"><i class="icon plus"></i></button>
|
||||
<button type="button" class="btn btn_text btn_40 gray_border only_icon" title="페이지 축소"><i class="icon minus"></i></button>
|
||||
<header class="header">
|
||||
<div class="inner pc_header">
|
||||
<h1 class="logo"><a href="./index.html" title="메인으로 이동"><img src="/kofair_case_seed/usr/images/layout/header_logo.png" alt="한국공정거래조정원 KOREA FAIR TRADE MEDIATION AGENCY"></a></h1>
|
||||
<div class="util_bar">
|
||||
<div class="search_area">
|
||||
<input type="text" class="input_text input_search" title="검색어 입력">
|
||||
<button type="button" class="btn btn_icon btn_search" title="검색버튼"><i class="icon search"></i></button>
|
||||
</div>
|
||||
<div class="util_btn_area">
|
||||
<button type="button" class="btn btn_text btn_40 blue_fill"><i class="icon sertification"></i>본인인증</button>
|
||||
<button type="button" class="btn btn_text btn_40 blue_fill hide"><i class="icon sertification_out"></i>인증해제</button>
|
||||
<button type="button" class="btn btn_text btn_40 gray_border only_icon" title="페이지 확대"><i class="icon plus"></i></button>
|
||||
<button type="button" class="btn btn_text btn_40 gray_border only_icon" title="페이지 축소"><i class="icon minus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="nav pc_header">
|
||||
<div class="inner">
|
||||
<ul class="menu_ul">
|
||||
<li class="menu_depth01">
|
||||
<a href="#">분쟁조정 안내</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
분쟁조정안내
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
<nav class="nav pc_header">
|
||||
<div class="inner">
|
||||
<ul class="menu_ul">
|
||||
<li class="menu_depth01">
|
||||
<a href="#">분쟁조정 안내</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
분쟁조정안내
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정 안내</a>
|
||||
<ul class="menu_depth03">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">준쟁조정관련 서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">자주 묻는 질문</a>
|
||||
<ul class="menu_depth03">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타) 절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">뉴스레터</a>
|
||||
<ul class="menu_depth03">
|
||||
<li><a href="#">뉴스레터 자료</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">공지사항</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">오시는 길</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정 안내</a>
|
||||
<ul class="menu_depth03">
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu_depth01">
|
||||
<a href="#">분쟁조정 신청</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
분쟁조정 신청
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정 신청하기</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정 사건조회</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu_depth01">
|
||||
<a href="#">분쟁조정 상담</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
분쟁조정 상담
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정 상담</a>
|
||||
<ul class="menu_depth03">
|
||||
<li><a href="#">분쟁조정 상담</a></li>
|
||||
<li><a href="#">무료법률 상담</a></li>
|
||||
<li class="outlink" target="_blank"><a href="#">실시간 상담</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu_depth01">
|
||||
<a href="#">분쟁조정 사례</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
분쟁조정 사례
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">공정거래</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">가맹사업거래</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">하도급거래</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">대규모유통업거래</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">불공정약관</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">대리점거래</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu_depth01">
|
||||
<a href="#">마이페이지</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
마이페이지
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">본인인증</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정신청현황</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정상담신청</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">법률상담예약</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn_40 only_icon btn_all_menu"><i class="icon menu"></i></button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="full_all_menu">
|
||||
<div class="top_area">
|
||||
<div class="inner">
|
||||
<h1 class="logo"><a href="./index.html" title="메인으로 이동"><img src="/kofair_case_seed/usr/images/layout/header_logo.png" alt="한국공정거래조정원 KOREA FAIR TRADE MEDIATION AGENCY"></a></h1>
|
||||
<button type="button" class="btn only_icon btn_menu_close"><i class="icon menu close"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="nav">
|
||||
<div class="inner">
|
||||
<ul class="menu_ul">
|
||||
<li>
|
||||
<a href="#none" class="menu_title">분쟁조정안내</a>
|
||||
<ul class="depth02_ul">
|
||||
<li>
|
||||
<a href="#none" class="depth02">분쟁조정 안내</a>
|
||||
<ul class="depth03_ul">
|
||||
<li><a href="#none">분쟁조정제도</a></li>
|
||||
<li><a href="#none">분쟁조정절차</a></li>
|
||||
<li><a href="#none">분쟁조정관련 서식</a></li>
|
||||
<li><a href="#none">법령자료실</a></li>
|
||||
<li><a href="#none">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="depth02">자주하는 질문</a>
|
||||
<ul class="depth03_ul">
|
||||
<li><a href="#none">조정신청 관련 질문</a></li>
|
||||
<li><a href="#none">절차진행 관련 질문</a></li>
|
||||
<li><a href="#none">(성립/불성립)절차종료 관련 질문</a></li>
|
||||
<li><a href="#none">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="depth02">뉴스레터</a>
|
||||
<ul class="depth03_ul">
|
||||
<li><a href="#none">뉴스레터 자료</a></li>
|
||||
<li><a href="#none">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="depth02">공지사항</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="depth02">오시는 길</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="menu_title">분쟁조정 신청</a>
|
||||
<ul class="depth02_ul">
|
||||
<li><a href="#none" class="depth02">분쟁조정 신청하기</a></li>
|
||||
<li><a href="#none" class="depth02">분쟁조정 사건조회</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="menu_title">분쟁조정 상담</a>
|
||||
<ul class="depth02_ul">
|
||||
<li>
|
||||
<a href="#none" class="depth02">상담신청</a>
|
||||
<ul class="depth03_ul">
|
||||
<li><a href="#none">분쟁조정 상담</a></li>
|
||||
<li><a href="#none">무료법률 상담</a></li>
|
||||
<li><a href="#none">실시간 상담 <i class="icon outlink"></i></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="menu_title">분쟁조정 사례</a>
|
||||
<ul class="depth02_ul">
|
||||
<li><a href="#none" class="depth02">공정거래</a></li>
|
||||
<li><a href="#none" class="depth02">가맹사업거래</a></li>
|
||||
<li><a href="#none" class="depth02">하도급거래</a></li>
|
||||
<li><a href="#none" class="depth02">대규모유통업거래</a></li>
|
||||
<li><a href="#none" class="depth02">불공정약관</a></li>
|
||||
<li><a href="#none" class="depth02">대리점 거래</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="menu_title">마이페이지</a>
|
||||
<ul class="depth02_ul">
|
||||
<li><a href="#none" class="depth02">본인인증</a></li>
|
||||
<li><a href="#none" class="depth02">분쟁조정 신청현황</a></li>
|
||||
<li><a href="#none" class="depth02">분쟁조정 상담신청</a></li>
|
||||
<li><a href="#none" class="depth02">법률상담예약</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="mobile_header">
|
||||
<div class="header_wrap">
|
||||
<h1 class="logo"><a href="./index.html" title="메인으로 이동"><img src="/kofair_case_seed/usr/images/layout/header_logo_m.png" alt="한국공정거래조정원 KOREA FAIR TRADE MEDIATION AGENCY"></a></h1>
|
||||
|
||||
<div class="header_util">
|
||||
<button type="button" class="btn only_icon btn_sertification"><i></i></button>
|
||||
<button type="button" class="btn only_icon btn_sertification_out hide"><i></i></button>
|
||||
<button type="button" class="btn only_icon btn_search"><i></i></button>
|
||||
<button type="button" class="btn only_icon btn_menu"><i></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search_area">
|
||||
<input type="text" class="input_text input_search">
|
||||
<button type="button" class="btn_search"><i></i></button>
|
||||
</div>
|
||||
<div class="all_menu_wrap">
|
||||
<div class="top_area">
|
||||
<h1 class="logo"><a href="./index.html" title="메인으로 이동"><img src="/kofair_case_seed/usr/images/layout/header_logo_m.png" alt="한국공정거래조정원 KOREA FAIR TRADE MEDIATION AGENCY"></a></h1>
|
||||
<button type="button" class="btn btn_menu_close"><i></i></button>
|
||||
</div>
|
||||
<nav class="mobile_menu">
|
||||
<ul class="mobile_ul">
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">분쟁조정 안내 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">준쟁조정관련 서식</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">자주 묻는 질문</a>
|
||||
<ul class="menu_depth03">
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타) 절차종결 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">뉴스레터</a>
|
||||
<ul class="menu_depth03">
|
||||
<li><a href="#">뉴스레터 자료</a></li>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">공지사항</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">오시는 길</a>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu_depth01">
|
||||
<a href="#">분쟁조정 신청</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
분쟁조정 신청
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정 신청하기</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정 사건조회</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu_depth01">
|
||||
<a href="#">분쟁조정 상담</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
분쟁조정 상담
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정 상담</a>
|
||||
<ul class="menu_depth03">
|
||||
<li><a href="#">분쟁조정 상담</a></li>
|
||||
<li><a href="#">무료법률 상담</a></li>
|
||||
<li class="outlink" target="_blank"><a href="#">실시간 상담</a></li>
|
||||
</li>
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">분쟁조정 신청 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu_depth01">
|
||||
<a href="#">분쟁조정 사례</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
분쟁조정 사례
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">공정거래</a>
|
||||
</li>
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">분쟁조정 상담 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">가맹사업거래</a>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">하도급거래</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">대규모유통업거래</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">불공정약관</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">대리점거래</a>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu_depth01">
|
||||
<a href="#">마이페이지</a>
|
||||
<div class="sub_menu_wrap">
|
||||
<div class="sub_menu">
|
||||
<div class="sub_menu_title_wrap">
|
||||
<h2>
|
||||
마이페이지
|
||||
<span>korea fair trade mediation agency</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="sub_menu_ul">
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">본인인증</a>
|
||||
</li>
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">분쟁조정 사례 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정신청현황</a>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">분쟁조정상담신청</a>
|
||||
</li>
|
||||
<li class="menu_depth02">
|
||||
<a href="#" class="sub_menu_title">법률상담예약</a>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn_40 only_icon btn_all_menu"><i class="icon menu"></i></button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="mobile_header">
|
||||
<div class="header_wrap">
|
||||
<h1 class="logo"><a href="./index.html" title="메인으로 이동"><img src="/kofair_case_seed/usr/images/layout/header_logo_m.png" alt="한국공정거래조정원 KOREA FAIR TRADE MEDIATION AGENCY"></a></h1>
|
||||
|
||||
<div class="header_util">
|
||||
<button type="button" class="btn only_icon btn_sertification"><i></i></button>
|
||||
<button type="button" class="btn only_icon btn_sertification_out hide"><i></i></button>
|
||||
<button type="button" class="btn only_icon btn_search"><i></i></button>
|
||||
<button type="button" class="btn only_icon btn_menu"><i></i></button>
|
||||
</li>
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">마이페이지 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search_area">
|
||||
<input type="text" class="input_text input_search">
|
||||
<button type="button" class="btn_search"><i></i></button>
|
||||
</div>
|
||||
<div class="all_menu_wrap">
|
||||
<div class="top_area">
|
||||
<h1 class="logo"><a href="./index.html" title="메인으로 이동"><img src="/kofair_case_seed/usr/images/layout/header_logo_m.png" alt="한국공정거래조정원 KOREA FAIR TRADE MEDIATION AGENCY"></a></h1>
|
||||
<button type="button" class="btn btn_menu_close"><i></i></button>
|
||||
</div>
|
||||
<nav class="mobile_menu">
|
||||
<ul class="mobile_ul">
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">분쟁조정 안내 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">분쟁조정 신청 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">분쟁조정 상담 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">분쟁조정 사례 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="m_menu_depth01_li">
|
||||
<button type="button" class="m_menu_depth01">마이페이지 <i></i></button>
|
||||
<ul class="m_sub_menu">
|
||||
<li>
|
||||
<a href="#" class="title">분쟁조정 안내</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">분쟁조정제도</a></li>
|
||||
<li><a href="#">분쟁조정절차</a></li>
|
||||
<li><a href="#">분쟁조정관련서식</a></li>
|
||||
<li><a href="#">법령자료실</a></li>
|
||||
<li><a href="#">자료실</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">자주 묻는 질문</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">조정신청 관련 질문</a></li>
|
||||
<li><a href="#">절차 진행 관련 질문</a></li>
|
||||
<li><a href="#">(성립/불성립) 절차 종료 관련 질문</a></li>
|
||||
<li><a href="#">(기타)절차종결 관련 질문</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="title">뉴스레터</a>
|
||||
<ul class="m_sub_depth02">
|
||||
<li><a href="#">뉴스레터</a></li>
|
||||
<li><a href="#">뉴스레터 서비스</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" class="title">공지사항</a></li>
|
||||
<li><a href="#" class="title">오시는 길</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
527
src/main/webapp/kofair_case_seed/usr/request/apl_05-2.html
Normal file
@ -0,0 +1,527 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>한국공정거래조정원 온라인분쟁조정시스템 > 분쟁조정 신청 > 분쟁조정 신청하기</title>
|
||||
|
||||
<!-- css -->
|
||||
<link rel="stylesheet" href="/kofair_case_seed/css/reset.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/css/font.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/usr/style/common.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/usr/style/layout.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/usr/style/popup.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/usr/style/style.css">
|
||||
<link rel="stylesheet" href="/kofair_case_seed/usr/style/request.css">
|
||||
|
||||
<!-- js -->
|
||||
<script src="/kofair_case_seed/script/lib/jquery-3.5.0.js"></script>
|
||||
<script src="/kofair_case_seed/usr/scripts/common.js"></script>
|
||||
<script src="/kofair_case_seed/usr/scripts/layout.js"></script>
|
||||
<script src="/kofair_case_seed/usr/scripts/popup.js"></script>
|
||||
<script src="/kofair_case_seed/usr/scripts/ui.js"></script>
|
||||
<script src="/kofair_case_seed/usr/scripts/request.js"></script>
|
||||
|
||||
<!-- 달력 -->
|
||||
<link rel="stylesheet" href="/kofair_case_seed/script/plugin/datapicker/default.css">
|
||||
<script src="/kofair_case_seed/script/plugin/datapicker/duet.js"></script>
|
||||
<script type="module" src="/kofair_case_seed/script/plugin/datapicker/duet.esm.js"></script>
|
||||
<script src="/kofair_case_seed/script/plugin/datapicker/duet.system.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="mask"></div>
|
||||
|
||||
<div class="wrap sub">
|
||||
<div data-include-path="/kofair_case_seed/usr/layout/_header.html"></div>
|
||||
|
||||
<div class="contents sub">
|
||||
|
||||
<div class="sub_visual">
|
||||
<h2>분쟁조정 신청</h2>
|
||||
</div>
|
||||
|
||||
<div class="inner">
|
||||
|
||||
<!-- lnb -->
|
||||
<div class="lnb">
|
||||
<p class="title">분쟁조정 신청</p>
|
||||
<ul class="lnb_menu">
|
||||
<li>
|
||||
<a href="#none" class="lnb_menu_title">분쟁조정 신청하기</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#none" class="lnb_menu_title">분쟁조정 사건조회</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- //lnb -->
|
||||
<div class="sub_content apl03_info_content
|
||||
apl05">
|
||||
|
||||
<!-- page_location -->
|
||||
<ul class="page_location">
|
||||
<li><i class="icon home"></i></li>
|
||||
<li>분쟁조정 신청</li>
|
||||
<li>분쟁조정 신청하기</li>
|
||||
</ul>
|
||||
<!-- //page_location -->
|
||||
|
||||
<h3 class="sub_con_tit">분쟁조정 신청하기</h3>
|
||||
<p class="sub_con_sub_tit">거래유형에 따른 분쟁조정 신청방법 및 절차를 알려드립니다.</p>
|
||||
|
||||
<ul class="process_step_wrap step5">
|
||||
<li>
|
||||
<p>STEP 01 <span>개인정보수집</span></p>
|
||||
<i class="icon step step01"></i>
|
||||
</li>
|
||||
<li>
|
||||
<p>STEP 02 <span>신청인 정보</span></p>
|
||||
<i class="icon step step02"></i>
|
||||
</li>
|
||||
<li>
|
||||
<p>STEP 03 <span>피신청인 정보</span></p>
|
||||
<i class="icon step step03"></i>
|
||||
</li>
|
||||
<li class="active">
|
||||
<p>STEP 04 <span>사건현황</span></p>
|
||||
<i class="icon step step04"></i>
|
||||
</li>
|
||||
<li>
|
||||
<p>STEP 05 <span>분쟁정보</span></p>
|
||||
<i class="icon step step05"></i>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- 사건현황 -->
|
||||
|
||||
<div class="table_top title mt0">
|
||||
<p class="title orange_border blue_border">사건현황</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 신청 확인사항 -->
|
||||
<div class="table_top" style="align-items:flex-end;">
|
||||
<p class="title depth02">신청 확인사항<span class="color_red">(필수)</span></p>
|
||||
<p class="cf_text">중복선택 불가</p>
|
||||
</div>
|
||||
|
||||
<dl class="blue_row_dl">
|
||||
<dt>
|
||||
1. 귀하는 '한국공정거래조정원'을 어떤 경로로 알게 되셨습니까?
|
||||
</dt>
|
||||
<dd class="request_check">
|
||||
<dl>
|
||||
<dt>1) 대중매체 광고</dt>
|
||||
<dd>
|
||||
<input type="radio" class="radio"><label for="">라디오 광고</label>
|
||||
</dd>
|
||||
<dd>
|
||||
<input type="radio" class="radio"><label for="">신문지면 광고</label>
|
||||
</dd>
|
||||
<dd>
|
||||
<input type="radio" class="radio"><label for="">TV 광고</label>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>2) SNS채널</dt>
|
||||
<dd>
|
||||
<input type="radio" class="radio"><label for="">네이버 공식 블로그</label>
|
||||
</dd>
|
||||
<dd>
|
||||
<input type="radio" class="radio"><label for="">유튜브(조정원TV)</label>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>3) 기타</dt>
|
||||
<dd>
|
||||
<input type="radio" class="radio"><label for="">직접입력</label>
|
||||
<input type="text" class="input_text hide">
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
<!-- //신청 확인사항 -->
|
||||
|
||||
<p class="title depth02">기타 확인사항</p>
|
||||
<!-- 기타 확인사항 -->
|
||||
<dl class="blue_row_dl etc_check_dl">
|
||||
<dt>1. 소송이 진행중인가요?</dt>
|
||||
<dd style="padding:0 30px 10px 30px;">
|
||||
<ul class="input_box" style="width:100%;">
|
||||
<li><input type="radio" class="radio"><label for="">미진행</label></li>
|
||||
<li style="align-self:flex-start;width:calc(100% - 100px);">
|
||||
<input type="radio" class="radio"><label for="">진행</label>
|
||||
<ul class="input_box" style="margin:20px 0 0 15px;width:90%;">
|
||||
<li style="width:calc((100% - 80px)/2);"><input type="text" class="input_text" placeholder="진행일 경우 법원명을 입력해주세요.">
|
||||
<p class="cf_text color_red">*진행일 경우 하단 증빙자료에 소장 반드시 첨부 요망</p>
|
||||
</li>
|
||||
<li style="width:calc((100% - 80px)/2);align-self:flex-start;"><input type="text" class="input_text" placeholder="진행일 경우 사건번호를 입력해주세요."></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>2. 소송 내용이 조정신청 내용과 동일한가요?</dt>
|
||||
<dd>
|
||||
<ul class="input_box">
|
||||
<li><input type="radio" class="radio"><label for="">동일하지 않음</label></li>
|
||||
<li>
|
||||
<input type="radio" class="radio"><label for="">동일</label>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>3. 타 협의회 조정이 진행중인가요?</dt>
|
||||
<dd>
|
||||
<ul class="input_box">
|
||||
<li><input type="radio" class="radio"><label for="">미진행</label></li>
|
||||
<li>
|
||||
<input type="radio" class="radio"><label for="">진행</label>
|
||||
<ul class="input_box">
|
||||
<li><input type="text" class="input_text" placeholder="진행일 경우 협의회명을 입력해주세요."></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>4. 중재가 진행중인가요?</dt>
|
||||
<dd>
|
||||
<ul class="input_box">
|
||||
<li><input type="radio" class="radio"><label for="">미진행</label></li>
|
||||
<li>
|
||||
<input type="radio" class="radio"><label for="">진행</label>
|
||||
<ul class="input_box">
|
||||
<li><input type="text" class="input_text" placeholder="진행일 경우 중재기관명을 입력해주세요."></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>5. 타 조정기구 조정이 진행중인가요?</dt>
|
||||
<dd>
|
||||
<ul class="input_box">
|
||||
<li><input type="radio" class="radio"><label for="">미진행</label></li>
|
||||
<li>
|
||||
<input type="radio" class="radio"><label for="">진행</label>
|
||||
<ul class="input_box">
|
||||
<li><input type="text" class="input_text" placeholder="진행일 경우 조정기구명을 입력해주세요."></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>6. 공정거래위원회 조사가 진행중인가요?</dt>
|
||||
<dd>
|
||||
<ul class="input_box">
|
||||
<li><input type="radio" class="radio"><label for="">미진행</label></li>
|
||||
<li>
|
||||
<input type="radio" class="radio"><label for="">진행</label>
|
||||
<ul class="input_box">
|
||||
<li><input type="text" class="input_text" placeholder="진행일 경우 담당부서명을 입력해주세요."></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>7. 당사자간 합의가 완료되어 조정조서 작성을 요청하는 사안인지 여부 </dt>
|
||||
<dd>
|
||||
<ul class="input_box">
|
||||
<li><input type="radio" class="radio"><label for="">아니오</label></li>
|
||||
<li>
|
||||
<input type="radio" class="radio"><label for="">예</label>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
<p class="cf_text" style="margin:15px 0 0 0;">※ 기타확인사항은 일반현황표 참조, 하단 증빙자료 첨부 또는 우편으로 별도 제출이 가능합니다.</p>
|
||||
<!-- //기타 확인사항 -->
|
||||
|
||||
<!-- 증빙자료 -->
|
||||
<b class="title depth03 blue_border">증빙자료 첨부시 선택해주세요.</b>
|
||||
<div class="table_type_rows">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:200px;">
|
||||
<col style="width:auto;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>증빙자료 첨부</th>
|
||||
<td><button type="button" class="btn btn_text btn_40 darkblue_border">파일선택</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- //증빙자료 -->
|
||||
|
||||
<!-- 협의회별 상세 현황 -->
|
||||
<div class="table_top title">
|
||||
<p class="title orange_border blue_border">협의회별 상세 현황</p>
|
||||
</div>
|
||||
<p class="title depth02">하도급 분야</p>
|
||||
<div class="table_type_rows">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>상시종업원수</th>
|
||||
<td><input type="text" class="input_text" style="width:93%;margin:0 4px 0 0;">명</td>
|
||||
<th>대/중소기업 구분</th>
|
||||
<td>
|
||||
<select name="" id="" class="select w100per">
|
||||
<option value="">선택</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>공사업등록여부</th>
|
||||
<td>
|
||||
<select name="" id="" class="select w100per">
|
||||
<option value="">선택</option>
|
||||
</select>
|
||||
</td>
|
||||
<th>시공능력 평가액</th>
|
||||
<td><input type="text" class="input_text w100per"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="cf_text">※ 우편용 신청서 참조</p>
|
||||
<!-- //협의회별 상세 현황 -->
|
||||
|
||||
<!-- 하도급대금 내역(신청인) -->
|
||||
<div class="table_top">
|
||||
<p class="title depth02">하도급대금 내역(신청인)</p>
|
||||
<div class="btn_wrap">
|
||||
<button type="button" class="btn btn_text btn_35 orange_border btn_delect_tr">첫번째 행 삭제</button>
|
||||
<button type="button" class="btn btn_text btn_35 orange_fill btn_add_tr">입력추가</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table_type_cols line price_table scroll_table subcontract_list">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:116px;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3">목적물인도(수)-기성청구서상</th>
|
||||
<th colspan="7">하도급대금수령(지급)</th>
|
||||
<th rowspan="3">비고</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan="2">구분</th>
|
||||
<th rowspan="2">일자</th>
|
||||
<th rowspan="2">금액</th>
|
||||
<th colspan="2">현금</th>
|
||||
<th colspan="3">어음</th>
|
||||
<th rowspan="2">총액</th>
|
||||
<th rowspan="2">미지급</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>일자</th>
|
||||
<th>금액</th>
|
||||
<th>지급일</th>
|
||||
<th>만기일</th>
|
||||
<th>금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="contract_input subcontract_list0">
|
||||
<td>
|
||||
<select name="" id="" class="select w100per">
|
||||
<option value="00">선택</option>
|
||||
<option value="subcontract_list01">가</option>
|
||||
<option value="subcontract_list02">나</option>
|
||||
<option value="subcontract_list03">다</option>
|
||||
<option value="subcontract_list04">라</option>
|
||||
<option value="subcontract_list05">마</option>
|
||||
<option value="subcontract_list06">바</option>
|
||||
<option value="subcontract_list07">사</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
</tr>
|
||||
<tr class="reason_tr subcontract_list0">
|
||||
<td>소계: 가</td>
|
||||
<td></td>
|
||||
<td class="text_right">870,000,000</td>
|
||||
<td></td>
|
||||
<td class="text_right">870,000,000</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text_right">2,000</td>
|
||||
<td class="text_right">2,000</td>
|
||||
<td class="text_right">35,000</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="total_tr">
|
||||
<td>계</td>
|
||||
<td></td>
|
||||
<td class="text_right">870,000,000</td>
|
||||
<td></td>
|
||||
<td class="text_right">870,000,000</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text_right">2,000</td>
|
||||
<td class="text_right">2,000</td>
|
||||
<td class="text_right">35,000</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<!-- //하도급대금 내역(신청인) -->
|
||||
|
||||
<!-- 도급대금 내역 내역(피신청인) -->
|
||||
<div class="table_top">
|
||||
<p class="title depth02">도급대금 내역(피신청인)</p>
|
||||
<div class="btn_wrap">
|
||||
<button type="button" class="btn btn_text btn_35 orange_border btn_delect_tr">첫번째 행 삭제</button>
|
||||
<button type="button" class="btn btn_text btn_35 orange_fill btn_add_tr">입력추가</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table_type_cols line price_table scroll_table contract_list">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:calc((100% - 116px)/10);">
|
||||
<col style="width:116px;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3">목적물인도(수)-기성청구서상</th>
|
||||
<th colspan="7">도급대금수령</th>
|
||||
<th rowspan="3">비고</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan="2">구분</th>
|
||||
<th rowspan="2">일자</th>
|
||||
<th rowspan="2">금액</th>
|
||||
<th colspan="2">현금</th>
|
||||
<th colspan="3">어음</th>
|
||||
<th rowspan="2">총액</th>
|
||||
<th rowspan="2">미지급</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>일자</th>
|
||||
<th>금액</th>
|
||||
<th>지급일</th>
|
||||
<th>만기일</th>
|
||||
<th>금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="contract_input contract_list0">
|
||||
<td>
|
||||
<select name="" id="" class="select w100per">
|
||||
<option value="00">선택</option>
|
||||
<option value="contract_list01">가</option>
|
||||
<option value="contract_list02">나</option>
|
||||
<option value="contract_list03">다</option>
|
||||
<option value="contract_list04">라</option>
|
||||
<option value="contract_list05">마</option>
|
||||
<option value="contract_list06">바</option>
|
||||
<option value="contract_list07">사</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td class="td_price"><input type="text" class="input_text" readonly></td>
|
||||
<td><input type="text" class="input_text" readonly></td>
|
||||
</tr>
|
||||
<tr class="reason_tr contract_list0">
|
||||
<td>소계: </td>
|
||||
<td></td>
|
||||
<td class="text_right">870,000,000</td>
|
||||
<td></td>
|
||||
<td class="text_right">870,000,000</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text_right">2,000</td>
|
||||
<td class="text_right">2,000</td>
|
||||
<td class="text_right">35,000</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="total_tr">
|
||||
<td>계</td>
|
||||
<td></td>
|
||||
<td class="text_right">870,000,000</td>
|
||||
<td></td>
|
||||
<td class="text_right">870,000,000</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text_right">2,000</td>
|
||||
<td class="text_right">2,000</td>
|
||||
<td class="text_right">35,000</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<!-- //도급대금 내역(피신청인) -->
|
||||
|
||||
<!-- //사건현황 -->
|
||||
|
||||
<div class="btn_wrap">
|
||||
<div class="area_left">
|
||||
<button type="button" class="btn btn_text btn_45 gray_fill">이전단계</button>
|
||||
</div>
|
||||
<div class="area_right">
|
||||
<button type="button" class="btn btn_text btn_45 darkgray_border">임시저장</button>
|
||||
<button type="button" class="btn btn_text btn_45 darkblue_fill">다음단계</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div data-include-path="/kofair_case_seed/usr/layout/_footer.html"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -23,6 +23,12 @@
|
||||
<script src="/kofair_case_seed/usr/scripts/ui.js"></script>
|
||||
<script src="/kofair_case_seed/usr/scripts/request.js"></script>
|
||||
|
||||
<!-- 달력 -->
|
||||
<link rel="stylesheet" href="/kofair_case_seed/script/plugin/datapicker/default.css">
|
||||
<script src="/kofair_case_seed/script/plugin/datapicker/duet.js"></script>
|
||||
<script type="module" src="/kofair_case_seed/script/plugin/datapicker/duet.esm.js"></script>
|
||||
<script src="/kofair_case_seed/script/plugin/datapicker/duet.system.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -52,7 +58,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
<!-- //lnb -->
|
||||
<div class="sub_content apl03_info_content">
|
||||
<div class="sub_content apl03_info_content
|
||||
apl05">
|
||||
|
||||
<!-- page_location -->
|
||||
<ul class="page_location">
|
||||
@ -89,18 +96,346 @@
|
||||
</ul>
|
||||
|
||||
<!-- 사건현황 -->
|
||||
<div class="table_top title">
|
||||
|
||||
<div class="table_top title mt0">
|
||||
<p class="title orange_border blue_border">사건현황</p>
|
||||
</div>
|
||||
|
||||
<p class="title depth02"></p>
|
||||
<!-- 대리인 정보 -->
|
||||
<p class="title depth02">대리인 정보</p>
|
||||
<div class="table_type_rows apl_info_input">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>상호</th>
|
||||
<td><input type="text" class="input_text w100per"></td>
|
||||
<th>대표자</th>
|
||||
<td><input type="text" class="input_text w100per"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>전화번호</th>
|
||||
<td>
|
||||
<div class="phone_wrap">
|
||||
<select name="" id="" title="전화번호 첫번째 자리 선택" class="select">
|
||||
<option value="">010</option>
|
||||
<option value="">011</option>
|
||||
</select> -
|
||||
<input type="text" class="input_text" title="전화번호 가운데 자리 입력"> -
|
||||
<input type="text" class="input_text" title="전화번호 마지막 자리 입력">
|
||||
</div>
|
||||
</td>
|
||||
<th>휴대폰</th>
|
||||
<td>
|
||||
<div class="phone_wrap">
|
||||
<select name="" id="" title="휴대폰 첫번째 자리 선택" class="select">
|
||||
<option value="">010</option>
|
||||
<option value="">011</option>
|
||||
</select> -
|
||||
<input type="text" class="input_text" title="휴대폰 가운데 자리 입력"> -
|
||||
<input type="text" class="input_text" title="휴대폰 마지막 자리 입력">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<p class="required_text">*<span>필수입력</span></p>이메일
|
||||
</th>
|
||||
<td colspan="3">
|
||||
<div class="email_wrap">
|
||||
<input type="text" class="input_text input_email" title="이메일 공급자 입력">
|
||||
@
|
||||
<input type="text" class="input_text input_email" title="이메일 공급자 입력">
|
||||
<select name="" id="" class="select email_select" title="이메일 공급자 선택">
|
||||
<option value="0">직접입력</option>
|
||||
<option value="chol.com">chol.com</option>
|
||||
<option value="daum.net">daum.net</option>
|
||||
<option value="dreamwiz.com">dreamwiz.com</option>
|
||||
<option value="empal.com">empal.com</option>
|
||||
<option value="freechal.com">freechal.com</option>
|
||||
<option value="gmail.com">gmail.com</option>
|
||||
<option value="hanafos.com">hanafos.com</option>
|
||||
<option value="hanmir.com">hanmir.com</option>
|
||||
<option value="hitel.com">hitel.com</option>
|
||||
<option value="hotmail.com">hotmail.com</option>
|
||||
<option value="korea.com">korea.com</option>
|
||||
<option value="lycos.co.kr">lycos.co.kr</option>
|
||||
<option value="nate.com">nate.com</option>
|
||||
<option value="naver.com">naver.com</option>
|
||||
<option value="netian.com">netian.com</option>
|
||||
<option value="paran.com">paran.com</option>
|
||||
<option value="yahoo.com">yahoo.com</option>
|
||||
<option value="yahoo.co.kr">yahoo.co.kr</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<p class="required_text">*<span>필수입력</span></p>우편번호
|
||||
</th>
|
||||
<td colspan="3">
|
||||
<ul class="input_box postcode_input">
|
||||
<li><input type="text" class="input_text" title="우편번호 입력"></li>
|
||||
<li><button type="button" class="btn btn_text darkblue_border btn_40">우편번호검색</button></li>
|
||||
<li><input type="checkbox" id="global_check" class="checkbox"><label for="global_check">해외시 체크해주세요.</label></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<p class="required_text">*<span>필수입력</span></p>도로명 주소
|
||||
</th>
|
||||
<td colspan="3">
|
||||
<ul class="input_box address_input">
|
||||
<li><input type="text" class="input_text" readonly></li>
|
||||
<li><input type="text" class="input_text" readonly></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="cf_text">※ 대리인 정보는 하단 증빙자료 첨부 또는 우편으로 별도 제출이 가능합니다.</p>
|
||||
|
||||
<!-- // 대리인 정보 -->
|
||||
|
||||
<!-- 주요재무현황 -->
|
||||
<p class="title depth02">주요재무현황</p>
|
||||
<div class="table_type_rows">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>자본금</th>
|
||||
<td><input type="text" class="input_text w100per"></td>
|
||||
<th>자산총액</th>
|
||||
<td><input type="text" class="input_text w100per"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>총매출액</th>
|
||||
<td><input type="text" class="input_text w100per"></td>
|
||||
<th>영업이익</th>
|
||||
<td><input type="text" class="input_text w100per"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="cf_text">※ 대리인 정보는 하단 증빙자료 첨부 또는 우편으로 별도 제출이 가능합니다.</p>
|
||||
<!-- // 주요재무현황 -->
|
||||
|
||||
<!-- 사건 확인사항 -->
|
||||
|
||||
<p class="title depth02">사건 확인사항</p>
|
||||
<div class="table_type_rows">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>최초 계약일</th>
|
||||
<td>
|
||||
<div class="calendar_wrap">
|
||||
<div class="calendar">
|
||||
<duet-date-picker identifier="date" name="" class="input_calendar"></duet-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<th>계약기간</th>
|
||||
<td>
|
||||
<div class="calendar_wrap calendar_term">
|
||||
<div class="calendar">
|
||||
<duet-date-picker identifier="date" name="" class="input_calendar start_date"></duet-date-picker>
|
||||
</div>
|
||||
~
|
||||
<div class="calendar">
|
||||
<duet-date-picker identifier="date" name="" class="input_calendar end_date"></duet-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>계약서 사본</th>
|
||||
<td colspan="3">
|
||||
<button class="btn btn_text btn_40 darkblue_border">파일선택</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="cf_text">※ 사건 확인사항은 일반현황표 참조, 하단 증빙자료 첨부 또는 우편으로 별도 제출이 가능합니다.</p>
|
||||
|
||||
<!-- //사건 확인사항 -->
|
||||
|
||||
<!-- 담당자 인적사항 -->
|
||||
|
||||
<div class="table_top" style="justify-content:flex-start;align-items:flex-end;">
|
||||
<p class="title depth02">담당자 인적사항</p>
|
||||
<ul class="input_box" style="margin:0 0 4px 30px;">
|
||||
<li><input type="radio" id="new" class="radio"><label for="new">새로입력</label></li>
|
||||
<li><input type="radio" id="same" class="radio"><label for="same">대표자와 동일</label></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="table_type_rows apl_info_input">
|
||||
<table style="border-top:2px solid #2e40ba;">
|
||||
<colgroup>
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
<col style="width:200px;">
|
||||
<col style="width:calc((100% - 400px)/2);">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>성함</th>
|
||||
<td><input type="text" class="input_text w100per"></td>
|
||||
<th>FAX</th>
|
||||
<td>
|
||||
<div class="phone_wrap fax_wrap">
|
||||
<select name="" id="" title="전화번호 첫번째 자리 선택" class="select">
|
||||
<option value="">010</option>
|
||||
<option value="">011</option>
|
||||
</select> -
|
||||
<input type="text" class="input_text" title="전화번호 가운데 자리 입력"> -
|
||||
<input type="text" class="input_text" title="전화번호 마지막 자리 입력">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>전화번호</th>
|
||||
<td>
|
||||
<div class="phone_wrap">
|
||||
<select name="" id="" title="전화번호 첫번째 자리 선택" class="select">
|
||||
<option value="">010</option>
|
||||
<option value="">011</option>
|
||||
</select> -
|
||||
<input type="text" class="input_text" title="전화번호 가운데 자리 입력"> -
|
||||
<input type="text" class="input_text" title="전화번호 마지막 자리 입력">
|
||||
</div>
|
||||
</td>
|
||||
<th>휴대폰</th>
|
||||
<td>
|
||||
<div class="phone_wrap">
|
||||
<select name="" id="" title="휴대폰 첫번째 자리 선택" class="select">
|
||||
<option value="">010</option>
|
||||
<option value="">011</option>
|
||||
</select> -
|
||||
<input type="text" class="input_text" title="휴대폰 가운데 자리 입력"> -
|
||||
<input type="text" class="input_text" title="휴대폰 마지막 자리 입력">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>부서/직위</th>
|
||||
<td colspan="3"><input type="text" class="input_text"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<p class="required_text">*<span>필수입력</span></p>이메일
|
||||
</th>
|
||||
<td colspan="3">
|
||||
<div class="email_wrap">
|
||||
<input type="text" class="input_text input_email" title="이메일 공급자 입력">
|
||||
@
|
||||
<input type="text" class="input_text input_email" title="이메일 공급자 입력">
|
||||
<select name="" id="" class="select email_select" title="이메일 공급자 선택">
|
||||
<option value="0">직접입력</option>
|
||||
<option value="chol.com">chol.com</option>
|
||||
<option value="daum.net">daum.net</option>
|
||||
<option value="dreamwiz.com">dreamwiz.com</option>
|
||||
<option value="empal.com">empal.com</option>
|
||||
<option value="freechal.com">freechal.com</option>
|
||||
<option value="gmail.com">gmail.com</option>
|
||||
<option value="hanafos.com">hanafos.com</option>
|
||||
<option value="hanmir.com">hanmir.com</option>
|
||||
<option value="hitel.com">hitel.com</option>
|
||||
<option value="hotmail.com">hotmail.com</option>
|
||||
<option value="korea.com">korea.com</option>
|
||||
<option value="lycos.co.kr">lycos.co.kr</option>
|
||||
<option value="nate.com">nate.com</option>
|
||||
<option value="naver.com">naver.com</option>
|
||||
<option value="netian.com">netian.com</option>
|
||||
<option value="paran.com">paran.com</option>
|
||||
<option value="yahoo.com">yahoo.com</option>
|
||||
<option value="yahoo.co.kr">yahoo.co.kr</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<p class="required_text">*<span>필수입력</span></p>우편번호
|
||||
</th>
|
||||
<td colspan="3">
|
||||
<ul class="input_box postcode_input">
|
||||
<li><input type="text" class="input_text" title="우편번호 입력"></li>
|
||||
<li><button type="button" class="btn btn_text darkblue_border btn_40">우편번호검색</button></li>
|
||||
<li><input type="checkbox" id="global_check" class="checkbox"><label for="global_check">해외시 체크해주세요.</label></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<p class="required_text">*<span>필수입력</span></p>도로명 주소
|
||||
</th>
|
||||
<td colspan="3">
|
||||
<ul class="input_box address_input">
|
||||
<li><input type="text" class="input_text" readonly></li>
|
||||
<li><input type="text" class="input_text" readonly></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="cf_text">※ 담당자 인적사항은 일반현황표 참조, 하단 증빙자료 첨부 또는 우편으로 별도 제출이 가능합니다.</p>
|
||||
<!-- //담당자 인적사항 -->
|
||||
|
||||
<!-- 증빙자료 -->
|
||||
<b class="title depth03 blue_border">증빙자료 첨부시 선택해주세요.</b>
|
||||
<div class="table_type_rows">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width:200px;">
|
||||
<col style="width:auto;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>증빙자료 첨부</th>
|
||||
<td><button type="button" class="btn btn_text btn_40 darkblue_border">파일선택</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- //증빙자료 -->
|
||||
|
||||
<!-- //사건현황 -->
|
||||
|
||||
|
||||
|
||||
<div class="btn_wrap">
|
||||
<div class="area_left">
|
||||
<button type="button" class="btn btn_text btn_45 gray_fill">이전단계</button>
|
||||
</div>
|
||||
<div class="area_right">
|
||||
<button type="button" class="btn btn_text btn_45 darkgray_border">임시저장</button>
|
||||
<button type="button" class="btn btn_text btn_45 darkblue_fill">다음단계</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
$(function () {
|
||||
if ($(".input_calendar").length > 0) {
|
||||
setTimeout(function () {
|
||||
calendar();
|
||||
}, 100)
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// header, footer 공통 영역 불러오기
|
||||
window.addEventListener('load', function () {
|
||||
@ -9,11 +16,450 @@ window.addEventListener('load', function () {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
header();
|
||||
el.outerHTML = this.responseText;
|
||||
|
||||
}
|
||||
};
|
||||
xhttp.open('GET', includePath, true);
|
||||
xhttp.send();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 달력
|
||||
function calendar() {
|
||||
|
||||
// 캘린더 고유 클래스 추가
|
||||
$(".input_calendar").each(function (idx, itm) {
|
||||
idx += 1;
|
||||
$(itm).addClass("input_calendar" + idx);
|
||||
});
|
||||
|
||||
$(".start_date").each(function (idx, itm) {
|
||||
idx += 1;
|
||||
$(itm).addClass("start_date" + idx);
|
||||
});
|
||||
|
||||
$(".end_date").each(function (idx, itm) {
|
||||
idx += 1;
|
||||
$(itm).addClass("end_date" + idx);
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
|
||||
calendarTitle(); // 달력 타이틀
|
||||
calednarCaption(); // 달력 caption
|
||||
}, 100)
|
||||
|
||||
|
||||
|
||||
// input value 값 추가, 검색 시 input value 값 안없어지게.
|
||||
var start_duetValue = $("duet-date-picker.start_date").val();
|
||||
var end_duetValue = $("duet-date-picker.end_date").val();
|
||||
var startcalendar_name = $(".start_date").attr("name");
|
||||
var endcalendar_name = $(".end_date").attr("name");
|
||||
|
||||
//달력 입력창 최대 입력 수 10자 제한('.' 포함)
|
||||
$("input.duet-date__input").attr("maxlength", "10");
|
||||
|
||||
var DATE_FORMAT = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
|
||||
var duetdateleng = $("duet-date-picker").length + 1;
|
||||
|
||||
var calendarNum = [];
|
||||
var startDateNum = [];
|
||||
var endDateNum = [];
|
||||
setTimeout(function () {
|
||||
for (var i = 1; i < duetdateleng; i++) {
|
||||
calendarNum[i] = document.querySelector(".input_calendar" + i);
|
||||
calendarNum[i].dateAdapter = {
|
||||
parse: function parse() {
|
||||
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
|
||||
var createDate = arguments.length > 1 ? arguments[1] : undefined
|
||||
var matches = value.match(DATE_FORMAT)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format: function format(date) {
|
||||
if (date.getMonth() < 9) {
|
||||
if (date.getDate() < 10) {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat('0', date.getMonth() + 1, ".")
|
||||
.concat('0', date.getDate())
|
||||
} else {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat('0', date.getMonth() + 1, ".")
|
||||
.concat(date.getDate())
|
||||
}
|
||||
} else {
|
||||
if (date.getDate() < 10) {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat(date.getMonth() + 1, ".")
|
||||
.concat('0', date.getDate())
|
||||
} else {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat(date.getMonth() + 1, ".")
|
||||
.concat(date.getDate())
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 달력 플러그인 실행
|
||||
calendarNum[i].localization = {
|
||||
placeholder: '날짜 입력',
|
||||
selectedDateMessage: 'Selected date is',
|
||||
prevMonthLabel: '이전 달 보기',
|
||||
nextMonthLabel: '다음 달 보기',
|
||||
monthSelectLabel: '달 선택',
|
||||
yearSelectLabel: '년도 선택',
|
||||
closeLabel: '달력 닫기',
|
||||
dayNames: ['일', '월', '화', '수', '목', '금', '토'],
|
||||
monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
identifier: "input_date",
|
||||
name: "input_date"
|
||||
}
|
||||
|
||||
// 달력 닫았을 때 input, input[type=hidden]에 value 값 넣어주기
|
||||
calendarNum[i].addEventListener("duetClose", function (e) {
|
||||
startDt = e.target;
|
||||
startDtVal = e.target.value;
|
||||
startSub = startDtVal.replace(/\-/g, '');
|
||||
var inputName = $(this).attr("name");
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
$("duet-date-picker .duet-date__input").each(function (idx, itm) {
|
||||
idx += 1;
|
||||
$(itm).attr("name", "input_date" + idx);
|
||||
$(itm).attr("id", "input_date" + idx);
|
||||
});
|
||||
$("duet-date-picker .duet-date__input").attr('onkeydown', 'this.value=dateSetting(this.value);');
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 시작날짜
|
||||
|
||||
for (var i = 1; i < duetdateleng; i++) {
|
||||
startDateNum[i] = document.querySelector(".start_date" + i);
|
||||
startDateNum[i].dateAdapter = {
|
||||
parse: function parse() {
|
||||
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
|
||||
var createDate = arguments.length > 1 ? arguments[1] : undefined
|
||||
var matches = value.match(DATE_FORMAT)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format: function format(date) {
|
||||
if (date.getMonth() < 9) {
|
||||
if (date.getDate() < 10) {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat('0', date.getMonth() + 1, ".")
|
||||
.concat('0', date.getDate())
|
||||
} else {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat('0', date.getMonth() + 1, ".")
|
||||
.concat(date.getDate())
|
||||
}
|
||||
} else {
|
||||
if (date.getDate() < 10) {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat(date.getMonth() + 1, ".")
|
||||
.concat('0', date.getDate())
|
||||
} else {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat(date.getMonth() + 1, ".")
|
||||
.concat(date.getDate())
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 달력 플러그인 실행
|
||||
startDateNum[i].localization = {
|
||||
placeholder: '날짜 입력',
|
||||
selectedDateMessage: 'Selected date is',
|
||||
prevMonthLabel: '이전 달 보기',
|
||||
nextMonthLabel: '다음 달 보기',
|
||||
monthSelectLabel: '달 선택',
|
||||
yearSelectLabel: '년도 선택',
|
||||
closeLabel: '달력 닫기',
|
||||
dayNames: ['일', '월', '화', '수', '목', '금', '토'],
|
||||
monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
identifier: "start_date",
|
||||
name: "start_date"
|
||||
}
|
||||
|
||||
// 달력 닫았을 때 input, input[type=hidden]에 value 값 넣어주기
|
||||
startDateNum[i].addEventListener("duetClose", function (e) {
|
||||
startDt = e.target;
|
||||
startDtVal = e.target.value;
|
||||
startSub = startDtVal.replace(/\-/g, '');
|
||||
var inputName = $(this).attr("name");
|
||||
$(".start_date").each(function (idx, itm) {
|
||||
$(this).find(".duet-date__input").attr("id", inputName + idx);
|
||||
$(this).find(".duet-date__input").attr("name", inputName);
|
||||
$(this).find(".duet-date__input").attr("value", startSub);
|
||||
$(this).find(".duet-date__input").next().attr("name", inputName + i + "_submit");
|
||||
$(this).find(".duet-date__input").next().attr("value", startSub);
|
||||
});
|
||||
});
|
||||
|
||||
//날짜 값 바꼈을 때 시작일, 종료일 찾아 alert 띄우기
|
||||
startDateNum[i].addEventListener("duetChange", function (e) {
|
||||
startDt = e.target;
|
||||
startDtVal = e.target.value;
|
||||
var n = i - 1;
|
||||
var endDtVal = $(".end_date" + n).find(".duet-date__input").val();
|
||||
endDtVal = endDtVal.replace(/[.]/gi, '');
|
||||
startDtVal = startDtVal.replace(/[.]/gi, '');
|
||||
if (startDtVal > endDtVal && endDtVal != "") {
|
||||
e.target.value = "";
|
||||
alert("시작일이 종료일보다 클 수 없습니다.");
|
||||
} else {}
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
$("duet-date-picker.start_date .duet-date__input").each(function (idx, itm) {
|
||||
idx += 1;
|
||||
$(itm).attr("name", "start_date" + idx);
|
||||
$(itm).attr("id", "start_date" + idx);
|
||||
});
|
||||
$("duet-date-picker .duet-date__input").attr('onkeydown', 'this.value=dateSetting(this.value);');
|
||||
}, 100)
|
||||
|
||||
startDateNum[i].addEventListener("duetFocus", function (e) {
|
||||
calendarSetting();
|
||||
});
|
||||
}
|
||||
|
||||
// 종료날짜
|
||||
|
||||
for (var i = 1; i < duetdateleng; i++) {
|
||||
endDateNum[i] = document.querySelector(".end_date" + i);
|
||||
endDateNum[i].dateAdapter = {
|
||||
parse: function parse() {
|
||||
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
|
||||
var createDate = arguments.length > 1 ? arguments[1] : undefined
|
||||
var matches = value.match(DATE_FORMAT)
|
||||
|
||||
if (matches) {
|
||||
return createDate(matches[3], matches[2], matches[1])
|
||||
}
|
||||
},
|
||||
format: function format(date) {
|
||||
if (date.getMonth() < 9) {
|
||||
if (date.getDate() < 10) {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat('0', date.getMonth() + 1, ".")
|
||||
.concat('0', date.getDate())
|
||||
} else {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat('0', date.getMonth() + 1, ".")
|
||||
.concat(date.getDate())
|
||||
}
|
||||
} else {
|
||||
if (date.getDate() < 10) {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat(date.getMonth() + 1, ".")
|
||||
.concat('0', date.getDate())
|
||||
} else {
|
||||
return ""
|
||||
.concat(date.getFullYear(), ".")
|
||||
.concat(date.getMonth() + 1, ".")
|
||||
.concat(date.getDate())
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 달력 플러그인 실행
|
||||
endDateNum[i].localization = {
|
||||
placeholder: '날짜 입력',
|
||||
selectedDateMessage: 'Selected date is',
|
||||
prevMonthLabel: '이전 달 보기',
|
||||
nextMonthLabel: '다음 달 보기',
|
||||
monthSelectLabel: '달 선택',
|
||||
yearSelectLabel: '년도 선택',
|
||||
closeLabel: '달력 닫기',
|
||||
dayNames: ['일', '월', '화', '수', '목', '금', '토'],
|
||||
monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
identifier: "end_date",
|
||||
name: "end_date"
|
||||
}
|
||||
|
||||
// 달력 닫았을 때 input, input[type=hidden]에 value 값 넣어주기
|
||||
endDateNum[i].addEventListener("duetClose", function (e) {
|
||||
endDt = e.target;
|
||||
endDtVal = e.target.value;
|
||||
endSub = endDtVal.replace(/\-/g, '');
|
||||
var inputName = $(this).attr("name");
|
||||
$(".end_date").each(function (idx, itm) {
|
||||
$(this).find(".duet-date__input").attr("id", inputName + idx);
|
||||
$(this).find(".duet-date__input").attr("name", inputName);
|
||||
$(this).find(".duet-date__input").attr("value", endSub);
|
||||
$(this).find(".duet-date__input").next().attr("name", inputName + i + "_submit");
|
||||
$(this).find(".duet-date__input").next().attr("value", endSub);
|
||||
});
|
||||
});
|
||||
|
||||
//날짜 값 바꼈을 때 시작일, 종료일 찾아 alert 띄우기
|
||||
endDateNum[i].addEventListener("duetChange", function (e) {
|
||||
endDt = e.target;
|
||||
endDtVal = e.target.value;
|
||||
var n = i - 1
|
||||
var startDtVal = $(".start_date" + n).find(".duet-date__input").val();
|
||||
startDtVal = startDtVal.replace(/[.]/gi, '');
|
||||
endDtVal = endDtVal.replace(/[.]/gi, '');
|
||||
//console.log("2",startDtVal,endDtVal);
|
||||
if (endDtVal < startDtVal) {
|
||||
e.target.value = "";
|
||||
alert("종료일이 시작일보다 작을 수 없습니다.");
|
||||
} else {}
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
$("duet-date-picker.end_date").each(function (idx, itm) {
|
||||
idx += 1;
|
||||
$(itm).attr("name", "end_date" + idx);
|
||||
$(itm).attr("id", "end_date" + idx);
|
||||
});
|
||||
$("duet-date-picker .duet-date__input").attr('onkeydown', 'this.value=dateSetting(this.value);');
|
||||
}, 100)
|
||||
|
||||
endDateNum[i].addEventListener("duetFocus", function (e) {
|
||||
calendarSetting();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}, 10)
|
||||
|
||||
function calendarMsgKey(ipt, startVal, endVal, startName, endName) {
|
||||
startVal = startVal.replace(/[.]/gi, '');
|
||||
endVal = endVal.replace(/[.]/gi, '');
|
||||
//console.log(startVal+"start", endVal+"end");
|
||||
if ($(ipt).is("[name=start_date]") > 0) {
|
||||
if (startVal > endVal && $(ipt).is("[name=start_date]") == true && endVal != "") {
|
||||
//console.log("1",startVal,endVal);
|
||||
alert("시작일이 종료일보다 클 수 없습니다.");
|
||||
ipt.value = "";
|
||||
} else if (startVal > endVal && $(ipt).is("[name=end_date]") == true && startVal != "") {
|
||||
//console.log("2",startVal,endVal);
|
||||
//console.log(ipt);
|
||||
if ($("duet-date-picker.end_date").val() == "") {
|
||||
|
||||
} else {
|
||||
alert("종료일이 시작일보다 작을 수 없습니다.");
|
||||
ipt.value = "";
|
||||
}
|
||||
} else {}
|
||||
} else {
|
||||
if (startVal > endVal && $(ipt).is("[name=" + startName + "]") == true && endVal != "") {
|
||||
//console.log("1",startVal,endVal);
|
||||
alert("시작일이 종료일보다 클 수 없습니다.");
|
||||
ipt.value = "";
|
||||
} else if (startVal > endVal && $(ipt).is("[name=" + endName + "]") == true && startVal != "") {
|
||||
//console.log("2",startVal,endVal);
|
||||
//console.log(ipt);
|
||||
if ($("duet-date-picker.end_date").val() == "") {
|
||||
|
||||
} else {
|
||||
alert("종료일이 시작일보다 작을 수 없습니다.");
|
||||
ipt.value = "";
|
||||
}
|
||||
} else {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function calendarSetting() {
|
||||
$('.calendar_wrap').each(function () {
|
||||
$(this).find('.duet-date__input').attr('onkeydown', 'this.value=dateSetting(this.value);');
|
||||
$(this).find('.duet-date__input').attr('onblur', 'this.value=dateSettingHere(this.value);');
|
||||
});
|
||||
}
|
||||
|
||||
function dateSetting(objValue) {
|
||||
var v = objValue.replace("..", ".");
|
||||
console.log(event.keyCode); // 한글쪽 - 189, 숫자키 쪽 109
|
||||
if (v.match(/^\d{4}$/) !== null) {
|
||||
if (event.keyCode == "8") {
|
||||
// 백스페이스 키를 누를 때 '.' 안생기게
|
||||
} else {
|
||||
v = v + '.';
|
||||
}
|
||||
} else if (v.match(/^\d{4}\.\d{2}$/) !== null) {
|
||||
if (event.keyCode == "8") {
|
||||
// 백스페이스 키를 누를 때 '.' 안생기게
|
||||
} else {
|
||||
v = v + '.';
|
||||
}
|
||||
}
|
||||
|
||||
// '-' 막기
|
||||
if (event.keyCode == "189" || event.keyCode == "109") {
|
||||
event.preventDefault();
|
||||
return v;
|
||||
} else {}
|
||||
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
function calednarCaption() {
|
||||
// 이전, 다음달 클릭 시 table caption 변경
|
||||
$(".duet-date__prev").on("click", function () {
|
||||
var monthText = $(this).closest(".duet-date__dialog-content").find(".duet-date__select--month").val();
|
||||
var yearText = $(this).closest(".duet-date__dialog-content").find(".duet-date__select--year").val();
|
||||
monthText = Number(monthText) + 1;
|
||||
monthText = monthText + "월";
|
||||
yearText = yearText + "년 ";
|
||||
$(this).closest(".duet-date__dialog-content").find(".duet-date__table caption").remove();
|
||||
$(this).closest(".duet-date__dialog-content").find(".duet-date__table").prepend("<caption>" + yearText + monthText + " 달력입니다.</caption>");
|
||||
});
|
||||
|
||||
$(".duet-date__next").on("click", function () {
|
||||
var monthText = $(this).closest(".duet-date__dialog-content").find(".duet-date__select--month").val();
|
||||
var yearText = $(this).closest(".duet-date__dialog-content").find(".duet-date__select--year").val();
|
||||
monthText = Number(monthText) + 1;
|
||||
monthText = monthText + "월";
|
||||
yearText = yearText + "년 ";
|
||||
$(this).closest(".duet-date__dialog-content").find(".duet-date__table caption").remove();
|
||||
$(this).closest(".duet-date__dialog-content").find(".duet-date__table").prepend("<caption>" + yearText + monthText + " 달력입니다.</caption>");
|
||||
});
|
||||
}
|
||||
|
||||
function calendarTitle() {
|
||||
setTimeout(function () {
|
||||
$(".start_date .duet-date__input").attr("title", "시작날짜를 YYYY.MM.DD 형식으로 입력해주세요");
|
||||
$(".end_date .duet-date__input").attr("title", "종료날짜를 YYYY.MM.DD 형식으로 입력해주세요");
|
||||
|
||||
//웹접근성>달력 버튼 title추가
|
||||
$(".duet-date__input-wrapper .duet-date__toggle").attr("title", "달력팝업 열림");
|
||||
}, 100)
|
||||
}
|
||||
@ -3,21 +3,21 @@ function header() {
|
||||
// ================= PC header =================
|
||||
$(".menu_depth01>a").on("mouseover",function () {
|
||||
$(this).siblings(".sub_menu_wrap").stop().slideDown(600);
|
||||
$(this).closest(".menu_depth01").siblings(".menu_depth01").find(".sub_menu_wrap").stop().slideUp(200);
|
||||
return false
|
||||
$(this).closest(".menu_depth01").siblings(".menu_depth01").find(".sub_menu_wrap").stop().slideUp(300);
|
||||
});
|
||||
|
||||
$(".sub_menu_wrap").mouseleave(function () {
|
||||
$(this).slideUp(10)
|
||||
$(this).stop().slideUp(300)
|
||||
});
|
||||
|
||||
// 웹접근성 - gnb
|
||||
$(".menu_depth01>a").focus(function () {
|
||||
$(this).siblings(".sub_menu_wrap").slideDown(600);
|
||||
});
|
||||
|
||||
$(".menu_depth01").each(function (idx, itm) {
|
||||
$(itm).find("a").each(function (idx2, itm2) {
|
||||
console.log(idx2, itm2, $(itm).find("a").length);
|
||||
// console.log(idx2, itm2, $(itm).find("a").length);
|
||||
if ($(itm).find("a").length - 1 == idx2) {
|
||||
$(itm2).addClass("last");
|
||||
} else if (idx2 == 0) {
|
||||
@ -26,7 +26,6 @@ function header() {
|
||||
});
|
||||
});
|
||||
|
||||
// 웹접근성 - gnb
|
||||
$(".first").keydown(function (e) {
|
||||
if (e.keyCode == "9") {
|
||||
if (e.shiftKey) {
|
||||
@ -48,12 +47,22 @@ function header() {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 전체메뉴
|
||||
$(".btn_all_menu").click(function(){
|
||||
$(".full_all_menu").addClass("active");
|
||||
});
|
||||
|
||||
$(".full_all_menu .btn_menu_close").click(function(){
|
||||
$(".full_all_menu").removeClass("active");
|
||||
})
|
||||
|
||||
// ================= 모바일 header =================
|
||||
|
||||
$(".m_sub_menu").slideUp(0)
|
||||
|
||||
// 모바일 전체메뉴
|
||||
$(".m_menu_depth01").click(function () {
|
||||
$(".m_menu_depth01").on("click",function () {
|
||||
$(this).next(".m_sub_menu").slideToggle(300);
|
||||
$(this).closest(".m_menu_depth01_li").toggleClass("active");
|
||||
|
||||
@ -72,7 +81,7 @@ function header() {
|
||||
if ($(this).is(".btn_search") == true) {
|
||||
$(".mobile_header .search_area").toggleClass("active");
|
||||
} else if ($(this).is(".btn_menu") == true) {
|
||||
$(".mobile_header .all_menu_wrap").toggleClass("active");
|
||||
$(".mobile_header .all_menu_wrap").addClass("active");
|
||||
}
|
||||
});
|
||||
|
||||
@ -93,7 +102,10 @@ function snb() {
|
||||
$(this).closest("li").siblings("li").removeClass("active");
|
||||
$(this).closest("li").siblings("li").find(".lnb_sub_menu").slideUp(400);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
$(function () {
|
||||
|
||||
@ -35,6 +35,89 @@ $(function () {
|
||||
$(".apl_list>li").last().find('.btn_delete_apl').attr("title", "신청인" + aplNum + " 삭제");
|
||||
}
|
||||
});
|
||||
|
||||
// 하도급대금 내역, 도급대금 내역 입력 추가 및 삭제
|
||||
|
||||
// var priceTr = `<tr class="contract_input"><td><select name="" id="" class="select w100per"><option value="">선택</option><option value="">가</option><option value="">나</option><option value="">다</option><option value="">라</option><option value="">마</option><option value="">바</option><option value="">사</option></select></td>`
|
||||
// priceTr +=`<td><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
// priceTr +=`<td><input type="text" class="input_text" readonly></td></tr>`;
|
||||
|
||||
|
||||
|
||||
$(".btn_add_tr").on("click", function () {
|
||||
|
||||
var trName = $(this).closest('.table_top').next(".price_table");
|
||||
trName = trName[0].classList[4];
|
||||
|
||||
var trLength = $(this).closest('.table_top').next(".price_table").find(".contract_input").length;
|
||||
|
||||
var priceTr = `<tr class="contract_input ` + trName + trLength + `"><td>`
|
||||
priceTr += `<select name="" id="" class="select w100per"><option value="00">선택</option><option value="` + trName + `01">가</option><option value="` + trName + `02">나</option><option value="` + trName + `03">다</option><option value="` + trName + `04">라</option><option value="` + trName + `05">마</option><option value="` + trName + `06">바</option><option value="` + trName + `07">사</option></select></td>`
|
||||
priceTr += `<td><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td class="td_price"><input type="text" class="input_text" readonly></td>`;
|
||||
priceTr += `<td><input type="text" class="input_text" readonly></td></tr>`;
|
||||
|
||||
$(this).closest('.table_top').next(".price_table").find("tbody").prepend(priceTr);
|
||||
|
||||
$(".contract_input .select").change(function () {
|
||||
if ($(this).val() !== "00") {
|
||||
var selectTitle = $(this).find(":selected").text();
|
||||
|
||||
var reasonTr = `<tr class="reason_tr ` + trName + trLength + `"><td>소계:` + selectTitle + `</td>`;
|
||||
reasonTr += `<td></td>`;
|
||||
reasonTr += `<td class="text_right"></td>`;
|
||||
reasonTr += `<td></td>`;
|
||||
reasonTr += `<td class="text_right"></td>`;
|
||||
reasonTr += `<td></td>`;
|
||||
reasonTr += `<td></td>`;
|
||||
reasonTr += `<td class="text_right"></td>`;
|
||||
reasonTr += `<td class="text_right"></td>`;
|
||||
reasonTr += `<td class="text_right"></td>`;
|
||||
reasonTr += `<td></td>`;
|
||||
reasonTr += `</tr>`;
|
||||
|
||||
if ($(".reason_tr." + trName + trLength).length == 0) {
|
||||
$(this).closest('.price_table').find("tbody").append(reasonTr);
|
||||
} else {
|
||||
$(this).closest(".price_table").find(".reason_tr." + trName + trLength).find("td").eq(0).text("소계:" + selectTitle);
|
||||
}
|
||||
$(this).closest(".contract_input").find(".input_text").removeAttr("readonly");
|
||||
} else {}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
$(".contract_input .select").change(function () {
|
||||
if ($(this).val() !== "00") {
|
||||
var selectTitle = $(this).find(":selected").text();
|
||||
console.log(selectTitle)
|
||||
$(this).closest(".contract_input").find(".input_text").removeAttr("readonly");
|
||||
var trClass = $(this).closest(".contract_input")[0].classList[1];
|
||||
if ($(".reason_tr." + trClass).length == 0) {} else {
|
||||
$(this).closest(".price_table").find(".reason_tr." + trClass).find("td").eq(0).text("소계:" + selectTitle);
|
||||
}
|
||||
} else {}
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
// 타이틀 클릭 시 하위 정보 노출/숨김
|
||||
@ -52,14 +135,15 @@ function removeLi(button) {
|
||||
if ($(itm1).closest(".apl_list").is(".rapl_list")) {
|
||||
$(itm1).html('피신청인' + idx1 + ' <i class="icon slide up"></i>');
|
||||
$(itm1).next(".btn_delete_apl").attr("title", "피신청인" + idx1 + " 삭제");
|
||||
}else{
|
||||
} else {
|
||||
$(itm1).html('신청인' + idx1 + ' <i class="icon slide up"></i>');
|
||||
$(itm1).next(".btn_delete_apl").attr("title", "신청인" + idx1 + " 삭제");
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function deleteReadOnly(select) {
|
||||
|
||||
}
|
||||
@ -6,8 +6,12 @@
|
||||
.btn_wrap.left{justify-content:flex-start;}
|
||||
.btn_wrap.center{justify-content:center;}
|
||||
|
||||
.btn_wrap .area_left,.btn_wrap .area_right{display:flex;gap:15px;}
|
||||
|
||||
|
||||
.btn{display:inline-block;color:#333;border-radius:5px;transition:all 0.2s ease-in-out;}
|
||||
.btn:hover{box-shadow:0 0 5px rgba(0,0,0,0.3);transition:all 0.2s ease-in-out;}
|
||||
.btn.only_icon:hover{box-shadow:none;}
|
||||
|
||||
.btn_35{height:35px;font-size:1.6rem;font-weight:400;padding:0 25px;}
|
||||
.btn_35.only_icon{width:35px;}
|
||||
@ -40,8 +44,12 @@
|
||||
.input_box{display:flex;align-items:center;gap:18px;flex-wrap:wrap;}
|
||||
.input_box.column{flex-direction:column;}
|
||||
|
||||
.input_text{height:40px;font-size:17px;color:#333;padding:0 10px;background:#fff;border:1px solid #d8d8d8;border-radius:5px;}
|
||||
.input_text:active{border:1px solid #333;}
|
||||
.input_text,.input_calendar .duet-date__input{height:40px;font-size:17px;color:#333;padding:0 10px;background:#fff;border:1px solid #d8d8d8;border-radius:5px;}
|
||||
.input_text:active,.input_calendar .duet-date__input:active{border:1px solid #333;}
|
||||
|
||||
.input_calendar .duet-date__toggle{width:38px;background:transparent url(/kofair_case_seed/usr/images/component/icon_calendar.png) no-repeat center center;background-size:18px 20px;border:0;box-shadow:none;}
|
||||
.input_calendar .duet-date__toggle svg{display:none;}
|
||||
.calendar_term{display:flex;align-items:center;gap:4px;}
|
||||
|
||||
.input_file{position:absolute;width:0;height:0;padding:0;overflow:hidden;border:0;}
|
||||
.input_file+.file{display:inline-flex;justify-content:center;align-items:center;}
|
||||
@ -64,6 +72,10 @@ input:disabled, input:read-only{background:#f8f9fa;border:1px solid #d8d8d8;}
|
||||
.fw_bold{font-weight:700 !important;}
|
||||
.fw_extrabold{font-weight:800 !important;}
|
||||
|
||||
.text_center{text-align:center;}
|
||||
.text_left{text-align:left;}
|
||||
.text_right{text-align:right;}
|
||||
|
||||
.gMarket_light{font-family:'GmarketSansLight';}
|
||||
.gMarket_medium{font-family:'GmarketSansMedium';}
|
||||
.gMarket_bold{font-family:'GmarketSansBold';}
|
||||
@ -137,11 +149,14 @@ input:disabled, input:read-only{background:#f8f9fa;border:1px solid #d8d8d8;}
|
||||
|
||||
/* 버튼 */
|
||||
.btn{border-radius:10px;}
|
||||
.btn:hover{box-shadow:none;}
|
||||
|
||||
.btn_35{height:70px;font-size:3.2rem;padding:0 50px;}
|
||||
.btn_40{height:80px;font-size:3.4rem;padding:0 38px;}
|
||||
.btn_45{height:90px;font-size:3.4rem;padding:0 36px;}
|
||||
.btn_50{height:100px;font-size:4rem;}
|
||||
|
||||
|
||||
/* 노출/숨김 */
|
||||
.pc_hide{display:inline-block;}
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ html{font-size:62.5%;}
|
||||
.header .nav .menu_ul{display:inline-flex;width:calc(100% - 43px);}
|
||||
.header .menu_depth01{width:calc(100% / 5);}
|
||||
.header .menu_depth01>a{display:flex;height:80px;font-size:2.3rem;font-weight:700;color:#333;text-align:center;align-items:center;justify-content:center;}
|
||||
.header .sub_menu_wrap{position:absolute;display:none;width:100%;left:0;background:#fff;border-top:1px solid #d8d8d8;margin:-1px 0 0 0;top:79px;box-shadow:0px 6px 6px rgba(0,0,0,.19);}
|
||||
.header .sub_menu_wrap{position:absolute;display:none;width:100%;left:0;background:#fff;border-top:1px solid #d8d8d8;margin:-1px 0 0 0;top:79px;box-shadow:0px 8px 8px rgba(0,0,0,.08);}
|
||||
.header .sub_menu{display:flex;}
|
||||
.header .sub_menu_wrap .sub_menu_title_wrap{width:400px;background:#2e40ba;color:#fff;text-align:right;padding:52px 70px 0 0;}
|
||||
.header .sub_menu_wrap .sub_menu_title_wrap h2{position:relative;font-size:3.3rem;font-weight:700;}
|
||||
@ -50,6 +50,28 @@ html{font-size:62.5%;}
|
||||
.header .btn_search:hover,.header .btn_all_menu:hover{box-shadow:none;}
|
||||
/* //header */
|
||||
|
||||
/* 전체메뉴 */
|
||||
.full_all_menu{position:fixed;width:100%;border-top:10px solid #171b70;background:#fff;top:-150vh;left:0;z-index:12;transition:top 0.3s ease-in-out;}
|
||||
.full_all_menu.active{top:0;transition:top 0.3s ease-in-out;}
|
||||
.full_all_menu .top_area{height:105px;justify-content:space-between;align-items:center;}
|
||||
.full_all_menu .top_area .inner{display:flex;height:100%;justify-content:space-between;align-items:center;}
|
||||
.full_all_menu .top_area .btn_menu_close{width:20px;height:20px;}
|
||||
.full_all_menu .top_area .btn_menu_close i.icon.menu.close{background:url(/kofair_case_seed/usr/images/component/icon_close.png) no-repeat center center;background-size:20px auto;}
|
||||
|
||||
.full_all_menu .nav{position:relative;display:flex;border-bottom:0;align-items:stretch;}
|
||||
.full_all_menu .nav::after{position:absolute;content:"";width:100%;height:4px;background:#2e40ba;top:80px;}
|
||||
.full_all_menu .menu_ul li{width:calc(100%/5);height:auto;text-align:center;}
|
||||
.full_all_menu .menu_title{display:flex;width:100%;height:80px;font-size:23px;font-weight:bold;color:#333;justify-content:center;align-items:center;}
|
||||
.full_all_menu .depth02_ul{display:flex;height:calc(100% - 95px);padding:32px 0 35px 30px;border-right:1px solid #d8d8d8;flex-direction:column;gap:30px;}
|
||||
.full_all_menu li:last-child .depth02_ul{border-right:0;}
|
||||
.full_all_menu .depth02_ul li{width:100%;text-align:left;}
|
||||
.full_all_menu .depth02{display:flex;font-size:21px;font-weight:500;color:#333;align-items:center;}
|
||||
.full_all_menu .depth03_ul{display:flex;margin:14px 0 0 0;font-size:19px;color:#666;flex-direction:column;gap:4px;}
|
||||
.full_all_menu .depth03_ul a{position:relative;min-height:30px;padding:0 0 0 10px;}
|
||||
.full_all_menu .depth03_ul a::after{position:absolute;content:"";width:4px;height:4px;background:#b4b4b4;left:0;top:12px;}
|
||||
|
||||
/* //전체메뉴 */
|
||||
|
||||
/* footer */
|
||||
.footer{width:100%;background:#d0d1d3;padding:40px 0 35px 0;font-size:1.7rem;font-weight:400;color:#585858;}
|
||||
.footer_content{display:flex;margin:40px 0 0 0;align-items:flex-end;justify-content:space-between;}
|
||||
@ -78,6 +100,9 @@ html{font-size:62.5%;}
|
||||
.pc_logo{display:none;}
|
||||
.m_logo{display:block;}
|
||||
|
||||
.full_all_menu{display:none !important;}
|
||||
|
||||
|
||||
/* header */
|
||||
|
||||
.header{border-top:0;}
|
||||
|
||||
@ -60,6 +60,36 @@
|
||||
.apl03_info_content .apl_info_input .phone_wrap .select{width:80px;}
|
||||
.apl03_info_content .apl_info_input .fax_wrap .input_text{width:calc((100% - 30px)/3);}
|
||||
|
||||
.apl03_info_content.apl05 .apl_info_input{display:block;}
|
||||
.apl03_info_content.apl05 .title.depth02:nth-child(6){margin:30px 0 0 0;}
|
||||
.apl03_info_content.apl05 .title.depth02{margin:50px 0 0 0;}
|
||||
.apl03_info_content.apl05 .table_top .title.depth02,.apl03_info_content.apl05 .table_top .btn_wrap{margin:0;}
|
||||
.apl03_info_content.apl05 .table_type_cols.line thead tr:nth-child(n+2) th:last-child{border-right:1px solid #d8d8d8;}
|
||||
.apl03_info_content.apl05 .table_type_cols.line thead tr:nth-child(2) th{height:40px;font-size:1.6rem;}
|
||||
.apl03_info_content.apl05 .table_type_cols.line thead tr:last-child th{height:40px;font-size:1.5rem;}
|
||||
.apl03_info_content.apl05 .table_type_cols.line .input_text{width:100%;}
|
||||
.apl03_info_content.apl05 .table_type_cols.line tr:hover td{text-decoration:none;}
|
||||
.apl03_info_content.apl05 .table_type_cols.line td .select{font-size:1.6rem;font-weight:400;text-align:left;padding:0 8px;background-position:calc(100% - 8px) 51%;}
|
||||
.apl03_info_content.apl05 td:first-child{font-size:1.5rem;font-weight:500;color:#333;}
|
||||
.apl03_info_content.apl05 td:nth-child(n+2){font-size:1.4rem;}
|
||||
.apl03_info_content.apl05 .title.blue_border.orange_border{margin:0;}
|
||||
.apl03_info_content.apl05 .blue_row_dl{display:flex;margin:20px 0 0 0;flex-direction:column;border-top:2px solid #2e40ba;}
|
||||
.apl03_info_content.apl05 .blue_row_dl>dt{display:flex;height:60px;padding:0 30px;font-size:1.7rem;font-weight:500;color:#333;background:#f4f5f7;border-bottom:1px solid #d8d8d8;align-items:center;}
|
||||
.apl03_info_content.apl05 .blue_row_dl>dd{display:flex;min-height:65px;padding:14px 30px;border-bottom:1px solid #d8d8d8;align-items:center;}
|
||||
.apl03_info_content.apl05 .blue_row_dl>dd.request_check{display:flex;align-items:flex-start;}
|
||||
.apl03_info_content.apl05 .blue_row_dl>dd.request_check dl{width:calc(70% / 3)}
|
||||
.apl03_info_content.apl05 .blue_row_dl .radio+label{font-size:1.7rem;font-weight:300;transform:skew(-0.1deg);}
|
||||
.apl03_info_content.apl05 .request_check dt{font-size:1.7rem;margin:0 0 14px 0;}
|
||||
.apl03_info_content.apl05 .request_check dd{padding:0 0 10px 16px;font-size:1.7rem;font-weight:300;}
|
||||
.apl03_info_content.apl05 .etc_check_dl dd>.input_box{width:100%;}
|
||||
.apl03_info_content.apl05 .etc_check_dl dd>.input_box>li:last-child{display:inline-flex;width:85%;align-items:center;}
|
||||
.apl03_info_content.apl05 .etc_check_dl dd>.input_box .input_box{width:calc(100% - 100px);margin:0 0 0 15px;}
|
||||
.apl03_info_content.apl05 .etc_check_dl dd>.input_box .input_box li{width:50%;}
|
||||
.apl03_info_content.apl05 .etc_check_dl dd>.input_box .input_box li .input_text{width:100%;}
|
||||
.apl03_info_content.apl05 .etc_check_dl dd>.input_box .cf_text.color_red{font-size:1.4rem;margin:5px 0 0 0;}
|
||||
.apl03_info_content.apl05 .title.blue_border{display:block;margin:50px 0 0 0;}
|
||||
.apl03_info_content.apl05 .title.blue_border::after{top:8px;}
|
||||
|
||||
/* 분쟁조정 사건조회 */
|
||||
.inquiry_content .box.gray_fill{padding:50px 20px;}
|
||||
.inquiry_content .input_box.column{width:500px;justify-content:center;margin:0 auto;}
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
/* table */
|
||||
.table_top{display:flex;align-items:center;justify-content:space-between;}
|
||||
.table_top.title{align-items:flex-end;}
|
||||
.table_top.title .depth02{font-size:2.2rem;}
|
||||
.table_top.title .cf_text{font-size:1.5rem;font-weight:400;color:#666;}
|
||||
.table_top .search_wrap{padding:0;margin:0;background:#fff;}
|
||||
.table_top .list_total_number{font-size:1.7rem;color:#666;}
|
||||
@ -73,10 +74,14 @@
|
||||
.table_type_cols table tbody td.table_number{color:#666;}
|
||||
.table_type_cols table tbody td.list_none{color:#666;}
|
||||
.table_type_cols table tbody td .list_title{color:#333;text-align:left;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;}
|
||||
.table_type_cols table tbody td .select{min-width:0;}
|
||||
.table_type_cols table tfoot tr{border-bottom:1px solid #d8d8d8;background:#f4f5f7;text-align:center;}
|
||||
.table_type_cols table tfoot tr td{height:50px;color:#333;vertical-align:middle;}
|
||||
|
||||
.table_type_cols.line th,.table_type_cols.line td{color:#333;border-right:1px solid #d8d8d8;}
|
||||
.table_type_cols.line td{font-weight:300;}
|
||||
.table_type_cols.line th,.table_type_cols.line td{color:#333;border-right:1px solid #d8d8d8;padding:0 5px;}
|
||||
.table_type_cols.line td{font-weight:300;color:#666;}
|
||||
.table_type_cols.line th:last-child,.table_type_cols.line td:last-child{border-right:0;}
|
||||
.table_type_cols.line tfoot td{font-weight:400;color:#333;}
|
||||
|
||||
.table_type_cols+.cf_text{margin:15px 0 0 0;}
|
||||
|
||||
@ -89,7 +94,6 @@
|
||||
.table_type_rows table .phone_wrap .input_text{width:calc((100% - 115px)/2);}
|
||||
.table_type_rows+.cf_text{margin:15px 0 0 0;}
|
||||
|
||||
|
||||
/* view */
|
||||
.list_view{margin:40px 0 0 0;}
|
||||
.list_view_tit{border-bottom:1px solid #c9c9c9;padding:24px;font-size:2.1rem;font-weight:bold;color:#333;border-top:3px solid #2e40ba;background:#f4f5f7;}
|
||||
@ -224,17 +228,17 @@
|
||||
|
||||
.table_type_cols table{font-size:3.4rem;}
|
||||
|
||||
.table_type_cols:not(.mobile_view_table) colgroup,.table_type_cols:not(.line) thead{display:none;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody tr{position:relative;display:flex;min-height:120px;padding:30px 40px 30px 120px;flex-wrap:wrap;align-items:center;justify-content:flex-start;box-sizing:border-box;gap:20px;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody tr.tr_list_none{justify-content:center;padding:0;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody td{max-width:50%;height:auto;order:5;background:none;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody td.list_none{display:flex;max-width:100%;height:40px;align-items:center;justify-content:center;text-align:center;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody .table_number{position:absolute;width:110px;left:0;top:50%;transform:translateY(-50%);}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody .td_title{width:100%;max-width:100%;order:1;white-space:wrap;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody .td_title a{display:-webkit-box;text-align:left;max-height:96px;line-height:1.4;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;white-space:wrap;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody .td_title a .pc_hide{text-align:left;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody .td_icon{display:inline-block !important;margin:-5px 13px 0 0;}
|
||||
.table_type_cols:not(.mobile_view_table) table tbody .td_hide{display:none;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) colgroup,.table_type_cols:not(.line) thead{display:none;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody tr{position:relative;display:flex;min-height:120px;padding:30px 40px 30px 120px;flex-wrap:wrap;align-items:center;justify-content:flex-start;box-sizing:border-box;gap:20px;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody tr.tr_list_none{justify-content:center;padding:0;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody td{max-width:50%;height:auto;order:5;background:none;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody td.list_none{display:flex;max-width:100%;height:40px;align-items:center;justify-content:center;text-align:center;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody .table_number{position:absolute;width:110px;left:0;top:50%;transform:translateY(-50%);}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody .td_title{width:100%;max-width:100%;order:1;white-space:wrap;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody .td_title a{display:-webkit-box;text-align:left;max-height:96px;line-height:1.4;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;white-space:wrap;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody .td_title a .pc_hide{text-align:left;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody .td_icon{display:inline-block !important;margin:-5px 13px 0 0;}
|
||||
.table_type_cols:not(.mobile_view_table, .scroll_table) table tbody .td_hide{display:none;}
|
||||
|
||||
.mobile_view_table table{margin:40px 0 0 0;border-top:4px solid #2e40ba;}
|
||||
.mobile_view_table table thead th,.mobile_view_table table tbody td{height:100px;border-bottom:2px solid #d8d8d8;border-right:2px solid #d8d8d8;}
|
||||
@ -257,6 +261,8 @@
|
||||
.table_type_rows table .email_wrap .email_select{width:calc(45% - 20px);}
|
||||
|
||||
.table_type_rows table+.cf_text{margin:30px 0 0 0;}
|
||||
|
||||
.scroll_table{overflow:auto;}
|
||||
|
||||
|
||||
/* view */
|
||||
|
||||