@domainkit/react 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.d.mts +138 -939
- package/dist/index.mjs +28 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -925,6 +925,8 @@ function useAccounts({ readOnly: refused, returnTo } = {}) {
|
|
|
925
925
|
};
|
|
926
926
|
}
|
|
927
927
|
const normalize = (value) => value.trim().toLowerCase().replace(/\.$/, "");
|
|
928
|
+
/** Whether `domain` is the zone or a name under it, compared on whole labels. */
|
|
929
|
+
const contains = (zone, domain) => domain === zone || domain.endsWith(`.${zone}`);
|
|
928
930
|
/**
|
|
929
931
|
* The zone a value already sits in: the longest listed zone the value is at or under. Two accounts
|
|
930
932
|
* can serve the same zone, so `preferred` names the one the customer picked and wins among equals;
|
|
@@ -933,10 +935,7 @@ const normalize = (value) => value.trim().toLowerCase().replace(/\.$/, "");
|
|
|
933
935
|
const placementOf = (value, zones, preferred) => {
|
|
934
936
|
const domain = normalize(value);
|
|
935
937
|
if (domain === "") return null;
|
|
936
|
-
const matching = zones.filter((candidate) =>
|
|
937
|
-
const zone = normalize(candidate.zone);
|
|
938
|
-
return domain === zone || domain.endsWith(`.${zone}`);
|
|
939
|
-
});
|
|
938
|
+
const matching = zones.filter((candidate) => contains(normalize(candidate.zone), domain));
|
|
940
939
|
const longest = matching.reduce((best, candidate) => best === null || normalize(candidate.zone).length > normalize(best.zone).length ? candidate : best, null);
|
|
941
940
|
if (longest === null) return null;
|
|
942
941
|
const closest = matching.filter((candidate) => normalize(candidate.zone) === normalize(longest.zone));
|
|
@@ -950,35 +949,39 @@ const placementOf = (value, zones, preferred) => {
|
|
|
950
949
|
};
|
|
951
950
|
};
|
|
952
951
|
/**
|
|
953
|
-
*
|
|
954
|
-
*
|
|
952
|
+
* Where a partly typed zone starts: the labels before it are the subdomain, the rest is the start
|
|
953
|
+
* of the zone. The partial zone begins on a label boundary, and a single trailing label counts only
|
|
954
|
+
* behind a single label, so "mail.ex" reaches for `example.com` while "probe.unrelated.sh" is a
|
|
955
|
+
* finished name rather than the start of `shop.com`.
|
|
955
956
|
*/
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
return
|
|
957
|
+
const partialSplit = (typed, zone) => {
|
|
958
|
+
if (zone.startsWith(typed)) return "";
|
|
959
|
+
for (let cut = typed.indexOf("."); cut >= 0; cut = typed.indexOf(".", cut + 1)) {
|
|
960
|
+
const head = typed.slice(0, cut);
|
|
961
|
+
const tail = typed.slice(cut + 1);
|
|
962
|
+
if (tail === "" || !zone.startsWith(tail)) continue;
|
|
963
|
+
if (tail.includes(".") || !head.includes(".")) return head;
|
|
964
|
+
}
|
|
965
|
+
return null;
|
|
965
966
|
};
|
|
966
|
-
/**
|
|
967
|
+
/**
|
|
968
|
+
* Zones the typed value is reaching for. A zone that already holds the name wins outright, the
|
|
969
|
+
* deepest first, so `probe.example.com` offers `example.com` and never a zone that only shares
|
|
970
|
+
* letters with it. Otherwise the value is still being typed: a zone it is a prefix of, or one whose
|
|
971
|
+
* start follows a subdomain.
|
|
972
|
+
*/
|
|
967
973
|
const suggestionsFor = (value, zones) => {
|
|
968
974
|
const typed = normalize(value);
|
|
969
975
|
if (typed === "") return zones;
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
const cut = typed.lastIndexOf(".");
|
|
974
|
-
if (cut < 0) return false;
|
|
975
|
-
const tail = typed.slice(cut + 1);
|
|
976
|
-
return tail !== "" && zone.startsWith(tail);
|
|
977
|
-
});
|
|
976
|
+
const holding = zones.filter((candidate) => contains(normalize(candidate.zone), typed));
|
|
977
|
+
if (holding.length > 0) return holding.toSorted((left, right) => normalize(right.zone).length - normalize(left.zone).length);
|
|
978
|
+
return zones.filter((candidate) => partialSplit(typed, normalize(candidate.zone)) !== null);
|
|
978
979
|
};
|
|
979
980
|
/** The value a suggestion completes to: whatever subdomain was typed, then the zone. */
|
|
980
981
|
const completionOf = (value, zone) => {
|
|
981
|
-
const
|
|
982
|
+
const typed = normalize(value);
|
|
983
|
+
const target = normalize(zone.zone);
|
|
984
|
+
const subdomain = contains(target, typed) ? typed.slice(0, Math.max(typed.length - target.length - 1, 0)) : partialSplit(typed, target) ?? "";
|
|
982
985
|
return subdomain === "" ? zone.zone : `${subdomain}.${zone.zone}`;
|
|
983
986
|
};
|
|
984
987
|
const optionId = (listId, zone) => `${listId}-${zone.connectionId}-${zone.zone}`;
|