storybook-addon-playground
Version:
A playground to enable consumers learn how to use the component library or to reproduce bugs
110 lines (107 loc) • 4.76 kB
JavaScript
import { autocompletion, startCompletion } from '@codemirror/autocomplete';
import { getEditorStateInfo } from '../utils/extensions-utils.js';
import { isInsideAttribute, getNewTagContext, shouldTriggerPropSuggestions, extractAlreadyUsedProps } from '../utils/autocomplete-utils.js';
function generateComponentNameCompletions(partialName, options) {
return Object.keys(options)
.filter((componentName) => componentName.toLowerCase().startsWith(partialName.toLowerCase()))
.map((componentName) => ({
label: componentName,
section: "Components",
type: "keyword",
apply: (view, completion, from, to) => {
// insert the component name and a space
const insertText = `${completion.label} `;
view.dispatch({
changes: { from, to, insert: insertText },
// move the cursor to the end of the new inserted text
selection: { anchor: from + insertText.length },
});
// we need to trigger autocompletion to immediately show prop completions after inserting the component name
requestAnimationFrame(() => {
startCompletion(view);
});
},
}));
}
function generatePropCompletions(componentName, options, partialPropName, usedProps) {
const componentProps = options[componentName];
if (!componentProps?.length) {
return [];
}
const normalizedProps = componentProps.flatMap((propItem) => Object.entries(propItem).map(([name, propInfo]) => ({ name, ...propInfo })));
return normalizedProps
.filter(({ name }) => !usedProps.has(name) && name.startsWith(partialPropName))
.map(({ name, type, required, defaultValue, description }) => ({
label: name,
boost: required ? 1 : 0, // move required props to the top of the list
detail: Array.isArray(type) ? type.join(" | ") : type,
// show description and default value in the completion tooltip
info: (description ? `${description}` : "") +
(description && defaultValue ? " | " : "") +
(defaultValue ? ` Defaults to: ${defaultValue}` : ""),
section: `${componentName}'s props`, // group props by component name
type: required ? "required" : "property", // property won't have * next to it
apply: (view, _completion, from, to) => {
const textToInsert = type === "string" ? `${name}=""` : `${name}={}`;
const replaceFrom = from - partialPropName.length;
const transaction = view.state.update({
changes: {
from: replaceFrom,
to,
insert: textToInsert,
},
selection: { anchor: replaceFrom + textToInsert.length - 1 },
});
view.dispatch(transaction); // Dispatch the transaction to apply changes and set cursor
},
}));
}
function playgroundAutocompletion(context, options) {
const { state } = context;
const { cursorPos, fullLineText, lineTextUpToCursor } = getEditorStateInfo(state);
let completions = [];
let from = cursorPos;
if (isInsideAttribute(fullLineText, cursorPos)) {
// never show suggestions from any kind inside attributes
return null;
}
const newTagName = getNewTagContext(lineTextUpToCursor);
if (newTagName !== null) {
// if the cursor is in a new tag context, generate component name completions
completions = generateComponentNameCompletions(newTagName, options);
if (newTagName !== "") {
from -= newTagName.length;
}
}
else {
// otherwise, generate prop completions
if (!shouldTriggerPropSuggestions(fullLineText, cursorPos)) {
return null;
}
const match = lineTextUpToCursor.match(/<(\w+)\s[\s\S]*?(\w*)$/);
if (!match) {
return null;
}
const usedProps = extractAlreadyUsedProps(fullLineText, cursorPos);
const [, componentName, partialPropName] = match;
completions = generatePropCompletions(componentName, options, partialPropName, usedProps);
}
if (completions.length === 0) {
return null;
}
return {
from,
to: cursorPos,
options: completions,
validFor: /^[\w-]*$/,
};
}
var playgroundAutocompletion$1 = (options) => autocompletion({
override: [
(context) => playgroundAutocompletion(context, options),
],
tooltipClass: () => "playground-autocompletion-dialog",
optionClass: () => "playground-autocompletion-option",
});
export { playgroundAutocompletion$1 as default };
//# sourceMappingURL=autocomplete.js.map