When you use alternation (the character |), the regex will try the alternatives from left to right, until find one that fits.
In this case, if the input is the string datetime, regex cannot find the match the first 16 alternatives (including the fourth - datetime(-local)$ - looking for something that ends with datetime-local, and see that parentheses make no difference here).
In alternative 17 (time$) she finds a match, since this passage means "ends with time", and datetime matches this. That’s why your regex returns true for the string datetime.
Your solution worked because you added ^, then the alternative "begins with datetime-local" now gives match - and datetime no more match because you’ve changed time$ (ends with time) for ^time (begins with time).
But anyway, if you want to check if a string matches exactly one of the names that are in regex, you can change it to:
const regExpTypes = new RegExp(
"^(button|color|date|datetime-local|email|file|hidden|image|month|password|radio|range|reset|search|submit|tel|text|time|url|week)$"
);
console.log(regExpTypes.test("datetime")); // false
console.log(regExpTypes.test("datetime-local")); // true
This regex has the markers ^ and $, which are respectively the beginning and end of the string. Then between them there is a toggle with all the alternatives that you want to capture.
Note that all the alternatives are in parentheses, because it makes a difference.
If I do, for example, ^button|color$, that means "start with button, or ends with color" - and if it starts with button, may even have more after; if it ends with color, may have more things before - see:
const regExpTypes = /^button|color$/;
console.log(regExpTypes.test("button")); // true
console.log(regExpTypes.test("button123")); // true
console.log(regExpTypes.test("color")); // true
console.log(regExpTypes.test("123color")); // true
If I ever put ^(button|color)$, that means "string start", followed by button or color, followed by the end of the string. That is, the string can only have what is in the alternatives, no more characters, no less (see).
const regExpTypes = /^(button|color)$/;
console.log(regExpTypes.test("button")); // true
console.log(regExpTypes.test("button123")); // false
console.log(regExpTypes.test("color")); // true
console.log(regExpTypes.test("123color")); // false
So if you want the string to be exactly one of the alternatives, use ^(alternativas)$.
You can simplify it a little bit more:
const regExpTypes = new RegExp(
"^(button|color|date(time-local)?|email|file|hidden|image|month|password|ra(dio|nge)|reset|search|submit|te(l|xt)|time|url|week)$"
);
console.log(regExpTypes.test("datetime")); // false
console.log(regExpTypes.test("datetime-local")); // true
I made some modifications:
date(time-local)?: the stretch time-local is in parentheses, and the ? soon after make it optional. So this stretch takes date or datetime-local (but not datetime)
ra(dio|nge): picks up the strings radio or range
te(l|xt): picks up the strings tel or text
Regex-free
But for this case it might be simpler to have an array with valid alternatives, and then check if the string is in this array, using the method includes:
const validos = ['button', 'color', 'date', 'datetime-local', 'email',
'file', 'hidden', 'image', 'month', 'password', 'radio',
'range', 'reset', 'search', 'submit', 'tel', 'text', 'time', 'url', 'week'];
console.log(validos.includes("datetime")); // false
console.log(validos.includes("datetime-local")); // true