const months = Object.fromEntries(Array.from(
{ length: 12 },
(_, i) => [ new Date(0, i).toLocaleString('ru-RU', { month: 'long' }), i ]
));
function parseDate(str) {
const [ , month, day, year, hour, minute ] = str.match(/(\S+) (\d+), (\d+) (\d+):(\d+)/);
return +new Date(year, months[month.toLowerCase()], day, hour, minute);
}
const Checkbox = forwardRef(({ label, ...props }, ref) =>
<label>
<input type="checkbox" ref={ref} {...props} />
{label}
</label>
);
function CheckboxGroup({
items,
label = item => item,
selected,
setSelected,
}) {
const onChange = ({ target: { checked, dataset: { index } } }) =>
setSelected(selected => checked
? [ ...selected, items[index] ]
: selected.filter(n => n !== items[index])
);
const allSelectedRef = useRef();
const onAllSelectedChange = ({ target: { checked } }) =>
setSelected(checked ? [...items] : []);
useEffect(() => {
const isAllSelected = items.length === selected.length;
allSelectedRef.current.checked = isAllSelected;
allSelectedRef.current.indeterminate = !isAllSelected && !!selected.length;
}, [ selected ]);
return (
<div className="checkbox-group">
<Checkbox
label="SELECT ALL"
defaultChecked={false}
onChange={onAllSelectedChange}
ref={allSelectedRef}
/>
{items.map((n, i) => (
<Checkbox
label={label(n)}
data-index={i}
checked={selected.includes(n)}
onChange={onChange}
/>
))}
</div>
);
}
const checkboxContainer = document.querySelector('.options');
const itemsContainer = document.querySelector('.box');
const hiddenClass = 'hidden';
:not
):function getCheckedSelector(container) {
const [ not, has ] = Array.prototype.reduce.call(
container.querySelectorAll('input'),
(acc, n) => (acc[+n.checked].push(`.${n.value}`), acc),
[ [], [] ]
);
return has.join('') + (not.length ? `:not(${not.join(',')})` : '');
}
checkboxContainer.addEventListener('change', function() {
const selector = getCheckedSelector(this);
for (const n of itemsContainer.children) {
n.classList.toggle(hiddenClass, !n.matches(selector));
}
});
const toggleHidden = (selector, state) => itemsContainer
.querySelectorAll(selector)
.forEach(n => n.classList.toggle(hiddenClass, state));
checkboxContainer.addEventListener('change', e => {
toggleHidden(':scope > *', true);
toggleHidden(getCheckedSelector(e.currentTarget), false);
});
const checkboxes = [...checkboxContainer.querySelectorAll('input')];
const onChange = function() {
this.forEach(({ classList: cl }) => {
cl.toggle(hiddenClass, checkboxes.some(n => n.checked !== cl.contains(n.value)));
});
}.bind([...itemsContainer.children]);
checkboxes.forEach(n => n.addEventListener('change', onChange));
const blocks = ref(Array.from({ length: 5 }, (_, i) => (-~i) ** 2));
const active = ref(0);
function next() {
active.value = (active.value + 1 + blocks.value.length) % blocks.value.length;
}
let intervalId = null;
onMounted(() => intervalId = setInterval(next, 500));
onUnmounted(() => clearInterval(intervalId));
<div
v-for="(n, i) in blocks"
v-text="n"
:class="[ 'box-item', { active: i === active } ]"
></div>
:nth-child
- не круто. Лучше сделать компонент, принимающий массив цветов и создающий блоки на его основе. Соответственно, вместо класса будет назначаться цвет фона напрямую, как-то так:<div
v-for="(n, i) in colors"
:style="{ backgroundColor: i === active ? n : '' }"
...
кликая на один список открывается и второй
почему при изменении даты передаваемой в пропс не обновляется значение в самом компоненте таймера?
targetDate
при изменении props.date
. Нет, можете себя не утруждать - ничего такого у вас нет.targetDate
, рассчитываем дни-часы-минуты-секунды сразу на основе props.date
- так при изменении props.date
не придётся предпринимать никаких дополнительных телодвижений, всё посчитается как надо при следующем вызове updateCountdown
. Вот как-то так. .data-accordion--summary-container.active > .dropdown {
display: block;
}
const itemSelector = '.data-accordion--summary-container';
const activeClass = 'active';
const toggle = el =>
el.parentNode.querySelectorAll(`:scope > ${itemSelector}`).forEach(n => {
n.classList[n === el ? 'toggle' : 'remove'](activeClass);
});
// применяем делегирование
document.body.addEventListener('click', ({ target: t }) => {
if (t.matches(itemSelector)) {
toggle(t);
}
});
// или, назначаем обработчик клика каждому элементу индивидуально
document.querySelectorAll(itemSelector).forEach(function(n) {
n.addEventListener('click', this);
}, e => e.currentTarget === e.target && toggle(e.target));
- acc[key] = [value];
+ (acc[key] ??= []).push(value);
const convertQueryStringToObject = str => Array
.from(new URLSearchParams(str))
.reduce((acc, n) => ((acc[n[0]] ??= []).push(n[1]), acc), {});
const convertQueryStringToObject = str => Array
.from(str.matchAll(/([^&]+)=([^&]+)/g))
.reduce((acc, [ , k, v ]) => (
Object.hasOwn(acc, k) || (acc[k] = []),
acc[k][acc[k].length] = v,
acc
), {});
.no-overflow {
overflow: hidden;
}
mounted() {
this.$watch(
() => this.isModalAddVisible || this.activeId,
val => document.body.classList.toggle('no-overflow', val),
{ immediate: true }
);
},
transition-group
. Вот так всё просто. const defaults = {
index: 0,
length: 0,
step: 1,
};
function Typewriter({ strings, delay }) {
const [ state, setState ] = useState(null);
useEffect(() => {
setState(() => ({...defaults}));
}, [ strings ]);
useEffect(() => {
const timeoutId = setTimeout(setState, delay, ({...state}) => {
state.length += state.step;
if (state.length === strings[state.index].length) {
state.step = -1;
} else if (state.length === 0) {
state.step = 1;
state.index = (state.index + 1) % strings.length;
}
return state;
});
return () => clearTimeout(timeoutId);
});
return <div>{strings?.[state?.index]?.slice(0, state?.length)}</div>;
}
работает, но кажется, что достаточно криво
let lvl = 0;
for (let i = LVLS.length; i--;) {
if (LVLS[i].exp <= EXP) {
lvl = LVLS[i].lv;
break;
}
}
переделать это во что-то более красивое
const lvl = LVLS.findLast(n => n.exp <= EXP)?.lv ?? 0;
const LVLS = [ 10, 25, 45, 70, 100 ];
const lvl = 1 + LVLS.findLastIndex(n => n <= EXP);
v-model
, кого показывать в модальном окне - отправляйте наверх эту информацию вместе с событием. Свойство, управляющее видимостью модального окна - пусть оно вместо логического значения хранит id или объект или что там у вас должно показываться в окне, если не null
, значит открываем окно. Вот так всё просто.v-model
, конечно только на уровне таблицы, в строках всё по-прежнему.<button data-country="">Показать всё</button>
<button data-country="France">Франция</button>
<button data-country="Germany">Германия</button>
<button data-country="England">Англия</button>
const catalogEl = document.querySelector('#catalog-box');
let catalogArr = [];
const buttons = document.querySelectorAll('button[data-country]');
buttons.forEach(n => n.addEventListener('click', onClick));
function onClick({ target: t }) {
buttons.forEach(n => n.classList.toggle('active', n === t));
renderCatalog(t.dataset.country);
}
function renderCatalog(country) {
const toRender = country
? catalogArr.filter(n => n.tag === country)
: catalogArr;
catalogEl.innerHTML = toRender
.map(n => `
<div class="catalog__card" id="${n.id}">
<img src="${n.img}" alt="${n.alt}" class="catalog__card_img">
<p class="catalog__card_author">${n.author}</p>
<p class="catalog__card_name">${n.name}</p>
<p class="catalog__card_note">${n.note}</p>
<p class="catalog__card_price">${n.price}</p>
<button class="catalog__card_btn">${n.btn}</button>
</div>`)
.join('');
}
fetch('catalogBox.json')
.then(r => r.json())
.then(r => {
catalogArr = r;
buttons[0].click();
});