В вашем регулярном выражении есть две проблемы
[,]+(?:[1-9]\d{0,2}|[1-3]\d{3}|40(?:[0-8]\d|9[0-6]))?
[,]+
- это позволит более чем 1 ,
соответствовать (?:[1-9]\d{0,2}|[1-3]\d{3}|40(?:[0-8]\d|9[0-6]))?
- так как этонеобязательно, поэтому ,,
будет соответствовать
. Вы можете использовать этот шаблон
^(?:(?:\d{1,3}|[0-3]\d{3}|40[0-8]\d|409[0-6]),)*?(?:(?:\d{1,3}|[0-3]\d{3}|40[0-8]\d|409[0-6]))$

Так что вв этом регулярном выражении идея состоит в том, что номер совпадения следует за нулем или более раз (сделайте это ленивым, чтобы последняя группа, в которой мы сопоставляем число без запятой), за которой следует число без запятой
Regex demo
const regex = /^(?:(?:\d{1,3}|[0-3]\d{3}|40[0-8]\d|409[0-6]),)*?(?:(?:\d{1,3}|[0-3]\d{3}|40[0-8]\d|409[0-6]))$/;
const strs = ['1234,234,4096', '1234,,234,4096,', '1234', '1234,', '4097', '4096']
strs.forEach(str => {
console.log(str, '\t\t\t', regex.test(str))
})
Редактировать: - хотя я забыл упомянуть, что он не может разрешить нули в начале.
^(?:(?:[1-9]\d{0,2}|[1-3]\d{3}|40[0-8]\d|409[0-6]),)*?(?:(?:[1-9]\d{0,2}|[1-3]\d{3}|40[0-8]\d|409[0-6]))$
Regex demo